INTROΒΆ
Notebook for applying Data Assimilation on modern data (Based on Caldarescu et al., 2021 data from Gulf of Panama)ΒΆ
Author: N.J. de Winter (n.j.de.winter@vu.nl)
Assistant Professor Vrije Universiteit Amsterdam
References used in coding
Data assimiliation
- Steiger, N.J., Hakim, G.J., Steig, E.J., Battisti, D.S., Roe, G.H., 2014. Assimilation of Time-Averaged Pseudoproxies for Climate Reconstruction. Journal of Climate 27, 426β441. https://doi.org/10.1175/JCLI-D-12-00693.1
- Hakim, G.J., Emile-Geay, J., Steig, E.J., Noone, D., Anderson, D.M., Tardif, R., Steiger, N., Perkins, W.A., 2016. The last millennium climate reanalysis project: Framework and first results. Journal of Geophysical Research: Atmospheres 121, 6745β6764. https://doi.org/10.1002/2016JD024751
- King, J., Tierney, J., Osman, M., Judd, E.J., Anchukaitis, K.J., 2023. DASH: a MATLAB toolbox for paleoclimate data assimilation. Geoscientific Model Development 16, 5653β5683. https://doi.org/10.5194/gmd-16-5653-2023
- Judd, E.J., Tierney, J.E., Lunt, D.J., MontaΓ±ez, I.P., Huber, B.T., Wing, S.L., Valdes, P.J., 2024. A 485-million-year history of Earthβs surface temperature. Science 385, eadk3705. https://doi.org/10.1126/science.adk3705
Data sources
- Caldarescu, D. E., Sadatzki, H., Andersson, C., SchΓ€fer, P., Fortunato, H., and Meckler, A. N.: Clumped isotope thermometry in bivalve shells: A tool for reconstructing seasonal upwelling, Geochimica et Cosmochimica Acta, 294, 174β191, https://doi.org/10.1016/j.gca.2020.11.019, 2021.
- GutiΓ©rrez, J.M., R.G. Jones, G.T. Narisma, L.M. Alves, M. Amjad, I.V.Gorodetskaya, M. Grose, N.A.B. Klutse, S. Krakovska, J. Li, D.MartΓnez-Castro, L.O. Mearns, S.H. Mernild, T. Ngo-Duc, B. van den Hurk, and J.-H. Yoon, 2021: Atlas. In Climate Change 2021: The Physical Science Basis. Contribution of Working Group I to the Sixth Assessment Report of the Intergovernmental Panel on Climate Change [Masson-Delmotte, V., P. Zhai, et al. (eds.)]. Cambridge University Press, Cambridge, United Kingdom and New York, NY, USA, pp. 1927β2058, doi:10.1017/9781009157896.021. Interactive Atlas available from http://interactive-atlas.ipcc.ch/
- Iturbide, M., FernΓ‘ndez, J., GutiΓ©rrez, J.M. et al. Implementation of FAIR principles in the IPCC: the WGI AR6 Atlas repository. Sci Data 9, 629 (2022). https://doi.org/10.1038/s41597-022-01739-y
- Boutin, J.; Vergely, J.-L.; Koehler, J.; Rouffi, F.; Reul, N. (2019): ESA Sea Surface Salinity Climate Change Initiative (Sea_Surface_Salinity_cci): Weekly Sea Surface Salinity product v1.8. Centre for Environmental Data Analysis, date of citation. https://catalogue.ceda.ac.uk/uuid/e5666094722c4ca787e323a9585b0a92
- SST Monitoring - Physical Monitoring | Smithsonian Tropical Research Institute, Physical Monitoring, URL
Calibration equations
- Graniero, L. E., Grossman, E. L., Robbins, J., Morales, J., Thompson, R., and OβDea, A.: Conus Shell Ξ΄13C values as proxies for Ξ΄13CDIC in tropical waters, Palaeogeography, Palaeoclimatology, Palaeoecology, 472, 119β127, https://doi.org/10.1016/j.palaeo.2017.02.007, 2017.
- DaΓ«ron, M. and Vermeesch, P.: Omnivariant generalized least squares regression: Theory, geochronological applications, and making the case for reconciled Ξ47 calibrations, Chemical Geology, 121881, https://doi.org/10.1016/j.chemgeo.2023.121881, 2023.
- Grossman, E. L. and Ku, T.-L.: Oxygen and carbon isotope fractionation in biogenic aragonite: temperature effects, Chemical Geology: Isotope Geoscience section, 59, 59β74, 1986.
- Gonfiantini, R., Stichler, W., and Rozanski, K.: Standards and intercomparison materials distributed by the International Atomic Energy Agency for stable isotope measurements, 1995.
- Dettman, D. L., Reische, A. K., and Lohmann, K. C.: Controls on the stable isotope composition of seasonal growth bands in aragonitic fresh-water bivalves (Unionidae), Geochimica et Cosmochimica Acta, 63, 1049β1057, 1999.
Load packagesΒΆ
InΒ [1]:
# Load packages
import numpy as np # The 'numpy' package is needed for matrix operations and calculations
import pandas as pd # The 'pandas' package helps us to import and manage data
import math as math # Math package for data cleaning
from scipy import stats # Import scipy.package for confidence intervals
from sklearn.preprocessing import StandardScaler # Import the package for standardizing data
import D47calib as D47c # Import the package for treating clumped isotope data by DaΓ«ron and Vermeesch (2023; https://github.com/mdaeron/D47calib)
import matplotlib.pyplot as plt # The 'matplotlib' package contains tools needed to plot our data and results
from matplotlib.patches import Rectangle # The 'Rectangle' function is used to add rectangles to our plots
import seaborn as sns # The 'seaborn' package is used to make our plots look nicer (e.g. enable heatmaps)
import warnings # The 'warnings' package is used to suppress warnings that might occur during the calculations
%matplotlib inline
InΒ [2]:
# Load model SST data as prior and show data structure
IPCC_Atlas_SST = pd.read_csv('Modern case/SSTdat.csv') # Load the data into Python and in the Jupyter environment.
# Pivot the data to have months as columns and grid cells (lat, lon) as rows
IPCC_Atlas_SST = IPCC_Atlas_SST.pivot(index=['lat', 'lon'], columns='month', values='tos').reset_index()
# Rename months to two-letter codes
month_mapping = {
'January': 'ja', 'February': 'fb', 'March': 'mr', 'April': 'ar', 'May': 'my',
'June': 'jn', 'July': 'jl', 'August': 'ag', 'September': 'sp', 'October': 'ot',
'November': 'nv', 'December': 'dc'
}
IPCC_Atlas_SST.rename(columns=month_mapping, inplace=True) # Rename the columns to two-letter codes for months
IPCC_Atlas_SST.head()
Out[2]:
| month | lat | lon | ar | ag | dc | fb | ja | jl | jn | mr | my | nv | ot | sp |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 7.5 | -81.5 | 27.870666 | 28.807864 | 28.234173 | 27.020926 | 27.569698 | 28.706740 | 28.716516 | 27.074181 | 28.717969 | 28.279754 | 28.346880 | 28.698233 |
| 1 | 7.5 | -80.5 | 26.808416 | 27.472491 | 28.242612 | 26.442010 | 27.180057 | 27.630609 | 27.446476 | 26.412426 | 27.232879 | 28.372525 | 27.958568 | 27.736655 |
| 2 | 7.5 | -79.5 | 26.814397 | 28.957019 | 28.453653 | 26.170235 | 27.295458 | 28.806538 | 28.734703 | 26.018813 | 28.156229 | 28.510441 | 28.605194 | 28.915533 |
| 3 | 7.5 | -78.5 | 26.891758 | 29.054418 | 28.537700 | 26.116582 | 27.303514 | 28.938615 | 28.816935 | 26.014780 | 28.164300 | 28.650586 | 28.788851 | 29.053953 |
| 4 | 7.5 | -77.5 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
Load monthly SAT model dataΒΆ
InΒ [3]:
# Load model SAT data as prior and show data structure
IPCC_Atlas_SAT = pd.read_csv('Modern case/SATdat.csv') # Load the data into Python and in the Jupyter environment.
# Pivot the data to have months as columns and grid cells (lat, lon) as rows
IPCC_Atlas_SAT = IPCC_Atlas_SAT.pivot(index=['lat', 'lon'], columns='month', values='tas').reset_index()
# Rename months to two-letter codes
month_mapping = {
'January': 'ja', 'February': 'fb', 'March': 'mr', 'April': 'ar', 'May': 'my',
'June': 'jn', 'July': 'jl', 'August': 'ag', 'September': 'sp', 'October': 'ot',
'November': 'nv', 'December': 'dc'
}
IPCC_Atlas_SAT.rename(columns=month_mapping, inplace=True) # Rename the columns to two-letter codes for months
IPCC_Atlas_SAT.head()
Out[3]:
| month | lat | lon | ar | ag | dc | fb | ja | jl | jn | mr | my | nv | ot | sp |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 7.5 | -81.5 | 27.583176 | 26.552208 | 26.558420 | 26.835735 | 26.682741 | 26.458174 | 26.592540 | 27.186361 | 27.313580 | 26.218864 | 26.229807 | 26.476027 |
| 1 | 7.5 | -80.5 | 27.275017 | 26.536266 | 26.511211 | 26.583742 | 26.550386 | 26.459099 | 26.570900 | 26.928097 | 27.066269 | 26.204203 | 26.266203 | 26.487984 |
| 2 | 7.5 | -79.5 | 27.001470 | 26.600628 | 26.552113 | 26.399340 | 26.497887 | 26.529715 | 26.607323 | 26.664957 | 26.917267 | 26.256363 | 26.311296 | 26.543280 |
| 3 | 7.5 | -78.5 | 26.625885 | 26.183826 | 26.066435 | 26.162770 | 26.128641 | 26.111752 | 26.153196 | 26.438007 | 26.403248 | 25.779915 | 25.811575 | 26.081343 |
| 4 | 7.5 | -77.5 | 26.058582 | 25.183080 | 24.992147 | 25.759280 | 25.274055 | 25.143060 | 25.183006 | 26.192318 | 25.495410 | 24.729294 | 24.765930 | 25.051905 |
Load monthly SSS model outcomes (HadGEM3-GC31-LL 1995-2014)ΒΆ
InΒ [4]:
# Load HadGEM3 SSS data as prior and show data structure
HadGEM3_SSS = pd.read_csv('Modern case/HadGEM3_SSS_ROI_Panama.csv') # Load the data into Python and in the Jupyter environment.
# Convert 'timeseries' column to datetime
HadGEM3_SSS['date'] = pd.to_datetime(HadGEM3_SSS['date'], format='%Y-%m-%d')
# Rename longitude and latitude columns to 'lon' and 'lat'
HadGEM3_SSS = HadGEM3_SSS.rename(columns={'longitude': 'lon', 'latitude': 'lat'})
# Extract month as two-letter code
month_abbr = ['ja', 'fb', 'mr', 'ar', 'my', 'jn', 'jl', 'ag', 'sp', 'ot', 'nv', 'dc']
HadGEM3_SSS['month'] = HadGEM3_SSS['date'].dt.month.apply(lambda x: month_abbr[x-1])
# Group by lat, lon, and month, then calculate the mean salinity for each group
HadGEM3_SSS = HadGEM3_SSS.groupby(['lat', 'lon', 'month'], as_index=False)['SSS'].mean()
# Pivot to make one column per month, keeping all data
HadGEM3_SSS = HadGEM3_SSS.pivot(index=['lat', 'lon'], columns='month', values='SSS').reset_index()
# Round latitudes to nearest whole value and subtract 0.5 to align with model grid
HadGEM3_SSS['lat'] = HadGEM3_SSS['lat'].round().astype(int) - 0.5
HadGEM3_SSS.head()
Out[4]:
| month | lat | lon | ag | ar | dc | fb | ja | jl | jn | mr | my | nv | ot | sp |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 7.5 | -84.5 | 30.189136 | 31.977873 | 31.473394 | 30.929856 | 30.345405 | 30.069491 | 30.195029 | 31.414025 | 32.039624 | 30.546265 | 29.788526 | 29.800602 |
| 1 | 7.5 | -83.5 | 29.721171 | 32.084429 | 30.293614 | 30.814989 | 29.829491 | 30.040615 | 29.755975 | 31.457839 | 31.915529 | 30.029132 | 29.261298 | 29.054229 |
| 2 | 7.5 | -82.5 | 29.425948 | 32.328173 | 29.279388 | 30.813826 | 29.614315 | 29.724405 | 29.525366 | 31.731921 | 31.874593 | 29.038743 | 28.927378 | 28.785658 |
| 3 | 7.5 | -81.5 | 29.718203 | 32.912211 | 29.224964 | 31.114077 | 29.523134 | 29.884337 | 29.375634 | 32.313844 | 31.924800 | 28.856813 | 28.677410 | 28.918779 |
| 4 | 7.5 | -80.5 | 28.278848 | 33.240089 | 28.165424 | 32.267726 | 30.310458 | 29.069918 | 28.696457 | 32.986556 | 31.064224 | 28.199434 | 27.682365 | 27.385693 |
Load monthly precipitation dataΒΆ
InΒ [5]:
# Load model precip data as prior and show data structure
IPCC_Atlas_precip = pd.read_csv('Modern case/precipdat.csv') # Load the data into Python and in the Jupyter environment.
# Pivot the data to have months as columns and grid cells (lat, lon) as rows
IPCC_Atlas_precip = IPCC_Atlas_precip.pivot(index=['lat', 'lon'], columns='month', values='pr').reset_index()
# Rename months to two-letter codes
month_mapping = {
'January': 'ja', 'February': 'fb', 'March': 'mr', 'April': 'ar', 'May': 'my',
'June': 'jn', 'July': 'jl', 'August': 'ag', 'September': 'sp', 'October': 'ot',
'November': 'nv', 'December': 'dc'
}
IPCC_Atlas_precip.rename(columns=month_mapping, inplace=True) # Rename the columns to two-letter codes for months
IPCC_Atlas_precip.head()
Out[5]:
| month | lat | lon | ar | ag | dc | fb | ja | jl | jn | mr | my | nv | ot | sp |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 7.5 | -81.5 | 1.689385 | 12.423080 | 2.590541 | 0.387124 | 0.582265 | 12.162777 | 11.670250 | 0.545877 | 6.474045 | 7.746123 | 11.768444 | 12.815062 |
| 1 | 7.5 | -80.5 | 1.779216 | 12.493553 | 2.881293 | 0.449284 | 0.714600 | 12.262433 | 11.679930 | 0.586808 | 6.634595 | 7.909942 | 11.458197 | 12.675345 |
| 2 | 7.5 | -79.5 | 2.001780 | 12.659093 | 3.562033 | 0.458198 | 0.814399 | 12.203677 | 11.351872 | 0.630577 | 7.039908 | 9.019142 | 12.075278 | 12.758909 |
| 3 | 7.5 | -78.5 | 2.500152 | 13.219075 | 4.115735 | 0.685854 | 1.062848 | 12.568253 | 11.503000 | 0.875989 | 7.622621 | 9.605698 | 12.277294 | 13.266212 |
| 4 | 7.5 | -77.5 | 3.386591 | 13.334969 | 4.033405 | 0.920459 | 1.207844 | 12.501793 | 11.494451 | 1.238471 | 8.514819 | 9.458278 | 12.520760 | 13.403852 |
Combine SST, SAT, SSS and precipitation data by lat and lonΒΆ
InΒ [6]:
# Merge the datasets of SST, SAT, SSS, and precipitation, force suffixes to be added to the column names
IPCC_Atlas = pd.merge(
IPCC_Atlas_SST.rename(columns={c: c + '_SST' for c in IPCC_Atlas_SST.columns if c not in ['lat', 'lon']}),
IPCC_Atlas_SAT.rename(columns={c: c + '_SAT' for c in IPCC_Atlas_SAT.columns if c not in ['lat', 'lon']}),
on=['lat', 'lon'],
how='outer'
)
IPCC_Atlas = pd.merge(
IPCC_Atlas,
HadGEM3_SSS.rename(columns={c: c + '_SSS' for c in HadGEM3_SSS.columns if c not in ['lat', 'lon']}),
on=['lat', 'lon'],
how='outer'
)
IPCC_Atlas = pd.merge(
IPCC_Atlas,
IPCC_Atlas_precip.rename(columns={c: c + '_precip' for c in IPCC_Atlas_precip.columns if c not in ['lat', 'lon']}),
on=['lat', 'lon'],
how='outer'
)
# Display the combined dataset
IPCC_Atlas.head()
Out[6]:
| month | lat | lon | ar_SST | ag_SST | dc_SST | fb_SST | ja_SST | jl_SST | jn_SST | mr_SST | ... | dc_precip | fb_precip | ja_precip | jl_precip | jn_precip | mr_precip | my_precip | nv_precip | ot_precip | sp_precip |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 7.5 | -84.5 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 1 | 7.5 | -83.5 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 2 | 7.5 | -82.5 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 3 | 7.5 | -81.5 | 27.870666 | 28.807864 | 28.234173 | 27.020926 | 27.569698 | 28.706740 | 28.716516 | 27.074181 | ... | 2.590541 | 0.387124 | 0.582265 | 12.162777 | 11.67025 | 0.545877 | 6.474045 | 7.746123 | 11.768444 | 12.815062 |
| 4 | 7.5 | -80.5 | 26.808416 | 27.472491 | 28.242612 | 26.442010 | 27.180057 | 27.630609 | 27.446476 | 26.412426 | ... | 2.881293 | 0.449284 | 0.714600 | 12.262433 | 11.67993 | 0.586808 | 6.634595 | 7.909942 | 11.458197 | 12.675345 |
5 rows Γ 50 columns
Calculate the monthly prior for model SST, SAT, SSS and precipitation valuesΒΆ
InΒ [7]:
# Create list of month names
months = ['ja', 'fb', 'mr', 'ar', 'my', 'jn', 'jl', 'ag', 'sp', 'ot', 'nv', 'dc']
# Prior SST, SAT, SSS & precipitation estimates from climate models (mean)
mu_prior_SAT_monthly = np.array(IPCC_Atlas[[f"{month}_SAT" for month in months]].mean(axis=0, skipna=True))
mu_prior_SST_monthly = np.array(IPCC_Atlas[[f"{month}_SST" for month in months]].mean(axis=0, skipna=True))
mu_prior_SSS_monthly = np.array(IPCC_Atlas[[f"{month}_SSS" for month in months]].mean(axis=0, skipna=True))
mu_prior_precip_monthly = np.array(IPCC_Atlas[[f"{month}_precip" for month in months]].mean(axis=0, skipna=True))
# Covariance between months in prior SST, SAT, SSS, and precip estimates from climate models (covariance matrix)
cov_prior_SAT_monthly = np.cov(IPCC_Atlas[[f"{month}_SAT" for month in months]].dropna(), rowvar=False)
cov_prior_SST_monthly = np.cov(IPCC_Atlas[[f"{month}_SST" for month in months]].dropna(), rowvar=False)
cov_prior_SSS_monthly = np.cov(IPCC_Atlas[[f"{month}_SSS" for month in months]].dropna(), rowvar=False)
cov_prior_precip_monthly = np.cov(IPCC_Atlas[[f"{month}_precip" for month in months]].dropna(), rowvar=False)
# Store copy of original prior means to keep when later updating the prior
mu_prior_SAT_monthly_original, cov_prior_SAT_monthly_original = mu_prior_SAT_monthly.copy(), cov_prior_SAT_monthly.copy()
mu_prior_SST_monthly_original, cov_prior_SST_monthly_original = mu_prior_SST_monthly.copy(), cov_prior_SST_monthly.copy()
mu_prior_SSS_monthly_original, cov_prior_SSS_monthly_original = mu_prior_SSS_monthly.copy(), cov_prior_SSS_monthly.copy()
mu_prior_precip_monthly_original, cov_prior_precip_monthly_original = mu_prior_precip_monthly.copy(), cov_prior_precip_monthly.copy()
# Extract the standard deviations (uncertainty) from the covariance matrix
std_prior_SAT_monthly = np.sqrt(np.diag(cov_prior_SAT_monthly))
std_prior_SST_monthly = np.sqrt(np.diag(cov_prior_SST_monthly))
std_prior_SSS_monthly = np.sqrt(np.diag(cov_prior_SSS_monthly))
std_prior_precip_monthly = np.sqrt(np.diag(cov_prior_precip_monthly))
print("SAT Monthly Means:", mu_prior_SAT_monthly)
print("SAT Monthly Std Devs:", std_prior_SAT_monthly)
print("SST Monthly Means:", mu_prior_SST_monthly)
print("SST Monthly Std Devs:", std_prior_SST_monthly)
print("SSS Monthly Means:", mu_prior_SSS_monthly)
print("SSS Monthly Std Devs:", std_prior_SSS_monthly)
print("Precip Monthly Means:", mu_prior_precip_monthly)
print("Precip Monthly Std Devs:", std_prior_precip_monthly)
SAT Monthly Means: [26.19674833 26.2856926 26.61780667 26.9355932 26.83360707 26.53449047 26.469068 26.5685366 26.55148913 26.3253362 26.23056327 26.30819593] SAT Monthly Std Devs: [0.47312434 0.41409864 0.41961986 0.45581423 0.52380544 0.58817398 0.60908107 0.63073153 0.65805864 0.67241717 0.66092167 0.57625065] SST Monthly Means: [27.44732929 26.77931444 26.66707259 27.07042812 27.81826605 28.38219858 28.62096962 28.7972226 29.09194208 29.15567638 28.99711452 28.42136266] SST Monthly Std Devs: [0.38677244 0.81134486 1.05313182 1.09679318 1.00268654 0.70288835 0.47347404 0.48010207 0.46942236 0.53116255 0.47287623 0.36093464] SSS Monthly Means: [32.9342162 33.62086235 34.05787824 34.33567287 33.89529622 32.50127564 32.15152274 32.05213394 31.54662009 31.37093452 31.85204499 32.38620964] SSS Monthly Std Devs: [2.75280482 2.29538948 2.0329931 1.88701351 2.35053874 3.0075095 2.69062003 2.87471767 3.03645453 2.85139432 2.7804909 2.98121591] Precip Monthly Means: [ 0.95326442 0.66065841 0.75870658 1.84590803 6.33325375 10.838048 11.84685843 11.9147063 11.8752223 10.88288037 8.05676985 3.13704099] Precip Monthly Std Devs: [0.34180364 0.27108401 0.23717466 0.51316878 0.91770655 1.08071996 1.1180354 1.19916169 1.19446726 1.14380572 0.86132612 0.62397088]
Plot the monthly priors for all model valuesΒΆ
InΒ [8]:
# Set dimensions of data
n_gridcells_monthly = len(IPCC_Atlas["lat"]) # Find the total number of models (use monthly data because monthly data has this column duplicated 3 times)
# Create a monthly scale for the x-axis
month_names = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] # List full month names
months_scale = np.arange(len(months)) + 1 # Create monthly scale
# Create the figure and axes
fig, axes = plt.subplots(2, 1, figsize=(10, 12), sharex=True)
# Panel 1: Plot the prior distribution for SST and SAT
axes[0].plot(months_scale, mu_prior_SAT_monthly, label='Prior SAT Mean', marker='o', color='r')
axes[0].plot(months_scale, mu_prior_SST_monthly, label='Prior SST Mean', marker='o', color='b')
# Add 95% confidence intervals for SAT
axes[0].fill_between(
months_scale,
mu_prior_SAT_monthly - stats.t.ppf(1 - 0.025, n_gridcells_monthly) * std_prior_SAT_monthly / np.sqrt(n_gridcells_monthly),
mu_prior_SAT_monthly + stats.t.ppf(1 - 0.025, n_gridcells_monthly) * std_prior_SAT_monthly / np.sqrt(n_gridcells_monthly),
alpha=0.2, color='r', label='SAT 95% CI'
)
# Add 95% confidence intervals for SST
axes[0].fill_between(
months_scale,
mu_prior_SST_monthly - stats.t.ppf(1 - 0.025, n_gridcells_monthly) * std_prior_SST_monthly / np.sqrt(n_gridcells_monthly),
mu_prior_SST_monthly + stats.t.ppf(1 - 0.025, n_gridcells_monthly) * std_prior_SST_monthly / np.sqrt(n_gridcells_monthly),
alpha=0.2, color='b', label='SST 95% CI'
)
axes[0].set_title('Prior Mean and 95% Confidence Interval for Monthly SST & SAT Values')
axes[0].set_ylabel('Temperature (Β°C)')
axes[0].legend()
axes[0].grid(True)
# Panel 2: Plot the prior distribution for SSS and precipitation
axes[1].plot(months_scale, mu_prior_SSS_monthly, label='Prior SSS Mean', marker='o', color='g')
ax2 = axes[1].twinx() # Create a secondary y-axis for precipitation
ax2.plot(months_scale, mu_prior_precip_monthly, label='Prior Precipitation Mean', marker='o', color='purple')
# Add 95% confidence intervals for SSS
axes[1].fill_between(
months_scale,
mu_prior_SSS_monthly - stats.t.ppf(1 - 0.025, n_gridcells_monthly) * std_prior_SSS_monthly / np.sqrt(n_gridcells_monthly),
mu_prior_SSS_monthly + stats.t.ppf(1 - 0.025, n_gridcells_monthly) * std_prior_SSS_monthly / np.sqrt(n_gridcells_monthly),
alpha=0.2, color='g', label='SSS 95% CI'
)
# Add 95% confidence intervals for precipitation
ax2.fill_between(
months_scale,
mu_prior_precip_monthly - stats.t.ppf(1 - 0.025, n_gridcells_monthly) * std_prior_precip_monthly / np.sqrt(n_gridcells_monthly),
mu_prior_precip_monthly + stats.t.ppf(1 - 0.025, n_gridcells_monthly) * std_prior_precip_monthly / np.sqrt(n_gridcells_monthly),
alpha=0.2, color='purple', label='Precipitation 95% CI'
)
axes[1].set_ylabel('SSS (psu)', color='g')
ax2.set_ylabel('Precipitation (mm/day)', color='purple')
axes[1].set_title('Prior Mean and 95% Confidence Interval for Monthly SSS & Precipitation Values')
axes[1].legend(loc='upper left')
ax2.legend(loc='upper right')
axes[1].grid(True)
# Update the x-axis with month names
axes[1].set_xticks(months_scale)
axes[1].set_xticklabels(month_names, rotation=45, ha="right")
Out[8]:
[Text(1, 0, 'January'), Text(2, 0, 'February'), Text(3, 0, 'March'), Text(4, 0, 'April'), Text(5, 0, 'May'), Text(6, 0, 'June'), Text(7, 0, 'July'), Text(8, 0, 'August'), Text(9, 0, 'September'), Text(10, 0, 'October'), Text(11, 0, 'November'), Text(12, 0, 'December')]
Convert SST and SAT model data to D47 domain using the regression by DaΓ«ron and Vermeesch (2023) and propagate uncertainty in the calibrationΒΆ
InΒ [9]:
# Apply T47()-function from the D47calib package to all SST columns
# Identify the SST and SAT columns separately
SST_columns = [col for col in IPCC_Atlas.columns if col.endswith('_SST')]
SAT_columns = [col for col in IPCC_Atlas.columns if col.endswith('_SAT')]
# Apply the conversion function to the SST and SAT columns and add new columns for D47 and D47_SE
for col in SST_columns:
base_col_name = col.replace('_SST', '') # Remove the '_SST' suffix from the column name
IPCC_Atlas[f'{base_col_name}_SST_D47'], IPCC_Atlas[f'{base_col_name}_SST_D47_SE'] = zip(*IPCC_Atlas[col].apply(
lambda x: D47c.OGLS23.T47(T = x) if not pd.isna(x) else (np.nan, np.nan)
)) # Use zip() to unpack the tuple returned by the apply() method and apply the T47()-function to each value in the column
for col in SAT_columns:
base_col_name = col.replace('_SAT', '') # Remove the '_SST' suffix from the column name
IPCC_Atlas[f'{base_col_name}_SAT_D47'], IPCC_Atlas[f'{base_col_name}_SAT_D47_SE'] = zip(*IPCC_Atlas[col].apply(
lambda x: D47c.OGLS23.T47(T = x) if not pd.isna(x) else (np.nan, np.nan)
)) # Use zip() to unpack the tuple returned by the apply() method and apply the T47()-function to each value in the column
# Display the combined data with D47 and D47_SE columns
D47_columns = [col for col in IPCC_Atlas.columns if col.endswith('_D47')]
D47_se_columns = [col for col in IPCC_Atlas.columns if '_D47_SE' in col]
print("D47 values for all grid cells:\n", IPCC_Atlas[D47_columns].head())
print("Calibration standard errors for all grid cells:\n", IPCC_Atlas[D47_se_columns].head())
D47 values for all grid cells: month ar_SST_D47 ag_SST_D47 dc_SST_D47 fb_SST_D47 ja_SST_D47 jl_SST_D47 \ 0 NaN NaN NaN NaN NaN NaN 1 NaN NaN NaN NaN NaN NaN 2 NaN NaN NaN NaN NaN NaN 3 0.584869 0.582139 0.583807 0.587368 0.585752 0.582432 4 0.587996 0.586037 0.583782 0.589082 0.586898 0.585573 month jn_SST_D47 mr_SST_D47 my_SST_D47 nv_SST_D47 ... dc_SAT_D47 \ 0 NaN NaN NaN NaN ... NaN 1 NaN NaN NaN NaN ... NaN 2 NaN NaN NaN NaN ... NaN 3 0.582404 0.587211 0.582399 0.583674 ... 0.588737 4 0.586114 0.589170 0.586742 0.583404 ... 0.588877 month fb_SAT_D47 ja_SAT_D47 jl_SAT_D47 jn_SAT_D47 mr_SAT_D47 my_SAT_D47 \ 0 NaN NaN NaN NaN NaN NaN 1 NaN NaN NaN NaN NaN NaN 2 NaN NaN NaN NaN NaN NaN 3 0.587915 0.588368 0.589034 0.588636 0.586880 0.586505 4 0.588662 0.588761 0.589032 0.588700 0.587642 0.587234 month nv_SAT_D47 ot_SAT_D47 sp_SAT_D47 0 NaN NaN NaN 1 NaN NaN NaN 2 NaN NaN NaN 3 0.589746 0.589713 0.588981 4 0.589790 0.589605 0.588946 [5 rows x 24 columns] Calibration standard errors for all grid cells: month ar_SST_D47_SE ag_SST_D47_SE dc_SST_D47_SE fb_SST_D47_SE \ 0 NaN NaN NaN NaN 1 NaN NaN NaN NaN 2 NaN NaN NaN NaN 3 0.001044 0.001042 0.001043 0.001046 4 0.001047 0.001045 0.001043 0.001048 month ja_SST_D47_SE jl_SST_D47_SE jn_SST_D47_SE mr_SST_D47_SE \ 0 NaN NaN NaN NaN 1 NaN NaN NaN NaN 2 NaN NaN NaN NaN 3 0.001045 0.001042 0.001042 0.001046 4 0.001046 0.001044 0.001045 0.001048 month my_SST_D47_SE nv_SST_D47_SE ... dc_SAT_D47_SE fb_SAT_D47_SE \ 0 NaN NaN ... NaN NaN 1 NaN NaN ... NaN NaN 2 NaN NaN ... NaN NaN 3 0.001042 0.001043 ... 0.001048 0.001047 4 0.001045 0.001043 ... 0.001048 0.001048 month ja_SAT_D47_SE jl_SAT_D47_SE jn_SAT_D47_SE mr_SAT_D47_SE \ 0 NaN NaN NaN NaN 1 NaN NaN NaN NaN 2 NaN NaN NaN NaN 3 0.001047 0.001048 0.001048 0.001046 4 0.001048 0.001048 0.001048 0.001046 month my_SAT_D47_SE nv_SAT_D47_SE ot_SAT_D47_SE sp_SAT_D47_SE 0 NaN NaN NaN NaN 1 NaN NaN NaN NaN 2 NaN NaN NaN NaN 3 0.001045 0.001049 0.001049 0.001048 4 0.001046 0.001049 0.001049 0.001048 [5 rows x 24 columns]
Estimate seawater oxygen isotope value from salinity based on modern Gulf of Panama d18Ow-salinity relationship by Graniero et al. (2017; rainy season)ΒΆ
InΒ [10]:
# Apply the d18Ow-SSS function from Graniero et al. (2017) to all SSS columns
# Identify the SSS columns
SSS_columns = [col for col in IPCC_Atlas.columns if col.endswith('_SSS')]
# Apply the conversion function to the SSS columns and add new columns for d18Ow and d18Ow_SE
for col in SSS_columns:
base_col_name = col.replace('_SSS', '') # Remove the '_SSS' suffix from the column name
IPCC_Atlas[f'{base_col_name}_SSS_d18Ow'] = IPCC_Atlas[col].apply(
lambda x: -7.89 + 0.23 * x if not pd.isna(x) else np.nan # Calculate d18Ow
)
# Display the combined data with d18Ow and d18Ow_SE columns
d18Ow_columns = [col for col in IPCC_Atlas.columns if col.endswith('_d18Ow')]
print("d18Ow values for all model outcomes:\n", IPCC_Atlas[d18Ow_columns].head())
d18Ow values for all model outcomes: month ag_SSS_d18Ow ar_SSS_d18Ow dc_SSS_d18Ow fb_SSS_d18Ow ja_SSS_d18Ow \ 0 -0.946499 -0.535089 -0.651119 -0.776133 -0.910557 1 -1.054131 -0.510581 -0.922469 -0.802553 -1.029217 2 -1.122032 -0.454520 -1.155741 -0.802820 -1.078707 3 -1.054813 -0.320192 -1.168258 -0.733762 -1.099679 4 -1.385865 -0.244780 -1.411953 -0.468423 -0.918595 month jl_SSS_d18Ow jn_SSS_d18Ow mr_SSS_d18Ow my_SSS_d18Ow nv_SSS_d18Ow \ 0 -0.974017 -0.945143 -0.664774 -0.520886 -0.864359 1 -0.980658 -1.046126 -0.654697 -0.549428 -0.983300 2 -1.053387 -1.099166 -0.591658 -0.558844 -1.211089 3 -1.016603 -1.133604 -0.457816 -0.547296 -1.252933 4 -1.203919 -1.289815 -0.303092 -0.745229 -1.404130 month ot_SSS_d18Ow sp_SSS_d18Ow 0 -1.038639 -1.035862 1 -1.159901 -1.207527 2 -1.236703 -1.269299 3 -1.294196 -1.238681 4 -1.523056 -1.591291
Calculate carbonate oxygen isotope value from SST and seawater oxygen isotope data using Grossman and Ku (1986) with the VPDB-VSMOW scale correction by Gonfiantini et al. (1995) and Dettman et al. (1999)ΒΆ
InΒ [11]:
# Iterate over each model and calculate d18Oc values
for index, row in IPCC_Atlas.iterrows():
# Iterate over each month
for month in months:
SST = row[f"{month}_SST"]
d18Ow = row[f"{month}_SSS_d18Ow"]
if not pd.isna(SST) and not pd.isna(d18Ow):
d18Oc = (20.6 - SST) / 4.34 + (d18Ow - 0.27)
else:
d18Oc = np.nan
# Add the calculated d18Oc value to the DataFrame
IPCC_Atlas.loc[index, f"{month}_d18Oc"] = d18Oc
# Display the updated DataFrame
IPCC_Atlas.head()
Out[11]:
| month | lat | lon | ar_SST | ag_SST | dc_SST | fb_SST | ja_SST | jl_SST | jn_SST | mr_SST | ... | mr_d18Oc | ar_d18Oc | my_d18Oc | jn_d18Oc | jl_d18Oc | ag_d18Oc | sp_d18Oc | ot_d18Oc | nv_d18Oc | dc_d18Oc |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 7.5 | -84.5 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 1 | 7.5 | -83.5 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 2 | 7.5 | -82.5 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 3 | 7.5 | -81.5 | 27.870666 | 28.807864 | 28.234173 | 27.020926 | 27.569698 | 28.706740 | 28.716516 | 27.074181 | ... | -2.219563 | -2.26546 | -2.687796 | -3.273769 | -3.154515 | -3.216026 | -3.374633 | -3.349191 | -3.292461 | -3.197284 |
| 4 | 7.5 | -80.5 | 26.808416 | 27.472491 | 28.242612 | 26.442010 | 27.180057 | 27.630609 | 27.446476 | 26.412426 | ... | -1.912361 | -1.94529 | -2.543542 | -3.137344 | -3.093875 | -3.239388 | -3.505681 | -3.488579 | -3.465035 | -3.442923 |
5 rows Γ 122 columns
Calculate the monthly prior for model SST- and SAT-derived D47 values and SSS-derived seawater oxygen isotope values with propagated uncertaintyΒΆ
InΒ [12]:
# Set the weights of the data based on the standard errors
weights_monthly_SST_D47 = 1 / IPCC_Atlas[[f"{month}_SST_D47_SE" for month in months]] ** 2
weights_monthly_SAT_D47 = 1 / IPCC_Atlas[[f"{month}_SAT_D47_SE" for month in months]] ** 2
# Change the column suffixes from "_D47_SE" to "_D47" in weights_monthly_SST_D47 to match the headers of the D47 matrix later for multiplication
weights_monthly_SST_D47.columns = [col.replace('_SST_D47_SE', '_SST_D47') for col in weights_monthly_SST_D47.columns]
weights_monthly_SAT_D47.columns = [col.replace('_SAT_D47_SE', '_SAT_D47') for col in weights_monthly_SAT_D47.columns]
# Prior D47 estimates from climate models (weighted mean)
mu_prior_SST_D47_monthly = np.array((IPCC_Atlas[[f"{month}_SST_D47" for month in months]] * weights_monthly_SST_D47).sum(axis = 0, skipna = True) / weights_monthly_SST_D47.sum(axis = 0, skipna = True)) # Calculate weighted monthly mean D47 values and convert to numpy array
mu_prior_SAT_D47_monthly = np.array((IPCC_Atlas[[f"{month}_SAT_D47" for month in months]] * weights_monthly_SAT_D47).sum(axis = 0, skipna = True) / weights_monthly_SAT_D47.sum(axis = 0, skipna = True)) # Calculate weighted monthly mean D47 values and convert to numpy array
# Calculate simple (unweighted) mean for monthly d18Ow values
mu_prior_SSS_d18Ow_monthly = np.array(IPCC_Atlas[[f"{month}_SSS_d18Ow" for month in months]].mean(axis=0, skipna=True))
mu_prior_d18Oc_monthly = np.array(IPCC_Atlas[[f"{month}_d18Oc" for month in months]].mean(axis=0, skipna=True))
# Decompose variance within and between model outcomes
model_variances_SST = IPCC_Atlas[[f"{month}_SST_D47" for month in months]].var(axis = 0, ddof = 1) # Compute variance across models
model_variances_SAT = IPCC_Atlas[[f"{month}_SAT_D47" for month in months]].var(axis = 0, ddof = 1) # Compute variance across models
model_variances_d18Ow = IPCC_Atlas[[f"{month}_SSS_d18Ow" for month in months]].var(axis = 0, ddof = 1) # Compute variance across models
model_variances_d18Oc = IPCC_Atlas[[f"{month}_d18Oc" for month in months]].var(axis = 0, ddof = 1) # Compute variance across models
measurement_variances_SST = (IPCC_Atlas[[f"{month}_SST_D47_SE" for month in months]] ** 2).mean(axis = 0, skipna = True) # Compute variance on measurements
measurement_variances_SAT = (IPCC_Atlas[[f"{month}_SAT_D47_SE" for month in months]] ** 2).mean(axis = 0, skipna = True) # Compute variance on measurements
# Covariance between months in prior D47 estimates from climate models (weighted covariance matrix)
cov_raw_monthly_SST = np.cov(IPCC_Atlas[[f"{month}_SST_D47" for month in months]].dropna(), rowvar = False) # Compute the covariance matrix for the raw data (without measurement uncertainty)
cov_raw_monthly_SAT = np.cov(IPCC_Atlas[[f"{month}_SAT_D47" for month in months]].dropna(), rowvar = False) # Compute the covariance matrix for the raw data (without measurement uncertainty)
cov_raw_monthly_d18Ow = np.cov(IPCC_Atlas[[f"{month}_SSS_d18Ow" for month in months]].dropna(), rowvar = False) # Compute the covariance matrix for the raw data (without measurement uncertainty)
cov_raw_monthly_d18Oc = np.cov(IPCC_Atlas[[f"{month}_d18Oc" for month in months]].dropna(), rowvar = False) # Compute the covariance matrix for the raw data (without measurement uncertainty)
cov_prior_SST_D47_monthly = cov_raw_monthly_SST.copy() # Copy covariance matrix to add uncertainty coming from the measurements
cov_prior_SAT_D47_monthly = cov_raw_monthly_SAT.copy() # Copy covariance matrix to add uncertainty coming from the measurements
np.fill_diagonal(cov_prior_SST_D47_monthly, np.diagonal(cov_raw_monthly_SST) + measurement_variances_SST) # Add diagonal terms for measurement uncertainties (which have no covariance between models)
np.fill_diagonal(cov_prior_SAT_D47_monthly, np.diagonal(cov_raw_monthly_SAT) + measurement_variances_SAT) # Add diagonal terms for measurement uncertainties (which have no covariance between models)
# Store copy of original prior means to keep when later updating the prior
mu_prior_SST_D47_monthly_original, cov_prior_SST_D47_monthly_original = mu_prior_SST_D47_monthly.copy(), cov_prior_SST_D47_monthly.copy()
mu_prior_SAT_D47_monthly_original, cov_prior_SAT_D47_monthly_original = mu_prior_SAT_D47_monthly.copy(), cov_prior_SAT_D47_monthly.copy()
mu_prior_SSS_d18Ow_monthly_original, cov_prior_SSS_d18Ow_monthly_original = mu_prior_SSS_d18Ow_monthly.copy(), cov_raw_monthly_d18Ow.copy()
mu_prior_d18Oc_monthly_original, cov_prior_d18Oc_monthly_original = mu_prior_d18Oc_monthly.copy(), cov_raw_monthly_d18Oc.copy()
# Extract the standard deviations (uncertainty) from the covariance matrix
std_prior_SST_D47_monthly = np.sqrt(np.diag(cov_prior_SST_D47_monthly))
std_prior_SAT_D47_monthly = np.sqrt(np.diag(cov_prior_SAT_D47_monthly))
std_prior_SSS_d18Ow_monthly = np.sqrt(np.diag(cov_raw_monthly_d18Ow))
std_prior_d18Oc_monthly = np.sqrt(np.diag(cov_raw_monthly_d18Oc))
# Print the results
print("Prior D47 estimates from SST in climate models (weighted mean):")
print(mu_prior_SST_D47_monthly)
print("Prior D47 estimates from SST in climate models (weighted covariance matrix):")
print(std_prior_SST_D47_monthly)
print("Prior D47 estimates from SAT in climate models (weighted mean):")
print(mu_prior_SAT_D47_monthly)
print("Prior D47 estimates from SAT in climate models (weighted covariance matrix):")
print(std_prior_SAT_D47_monthly)
print("Prior d18Ow estimates from SSS in climate models (weighted mean):")
print(mu_prior_SSS_d18Ow_monthly)
print("Prior d18Ow estimates from SSS in climate models (weighted covariance matrix):")
print(std_prior_SSS_d18Ow_monthly)
print("Prior d18Oc estimates from SST and d18Ow in climate models (weighted mean):")
print(mu_prior_d18Oc_monthly)
print("Prior d18Oc estimates from SST and d18Ow in climate models (weighted covariance matrix):")
print(std_prior_d18Oc_monthly)
Prior D47 estimates from SST in climate models (weighted mean): [0.58611136 0.58807854 0.58840607 0.58721478 0.58502217 0.58337762 0.5826824 0.58217063 0.58131734 0.58113373 0.58159202 0.58326221] Prior D47 estimates from SST in climate models (weighted covariance matrix): [0.00154225 0.00262358 0.0033056 0.00341755 0.00312591 0.00229783 0.00172489 0.00174205 0.00171518 0.00185845 0.00171988 0.00147969] Prior D47 estimates from SAT in climate models (weighted mean): [0.58980992 0.58954575 0.58855954 0.5876189 0.58791991 0.58880551 0.58899953 0.58870432 0.58875453 0.58942555 0.5897075 0.5894774 ] Prior D47 estimates from SAT in climate models (weighted covariance matrix): [0.0017582 0.00161882 0.00162767 0.00170748 0.0018731 0.00203748 0.00209141 0.00214556 0.00221785 0.00226071 0.00223188 0.00201213] Prior d18Ow estimates from SSS in climate models (weighted mean): [-0.31513027 -0.15720166 -0.056688 0.00720476 -0.09408187 -0.4147066 -0.49514977 -0.51800919 -0.63427738 -0.67468506 -0.56402965 -0.44117178] Prior d18Ow estimates from SSS in climate models (weighted covariance matrix): [0.63314511 0.52793958 0.46758841 0.43401311 0.54062391 0.69172719 0.61884261 0.66118506 0.69838454 0.65582069 0.63951291 0.68567966] Prior d18Oc estimates from SST and d18Ow in climate models (weighted mean): [-2.01976487 -1.72743135 -1.62606948 -1.68513392 -2.01588617 -2.38789044 -2.49384006 -2.58185743 -2.76264532 -2.80414742 -2.66091864 -2.4305927 ] Prior d18Oc estimates from SST and d18Ow in climate models (weighted covariance matrix): [0.59701115 0.37039599 0.285586 0.32615673 0.59423465 0.70492047 0.60946861 0.67389034 0.68550033 0.61192669 0.65277008 0.79771143]
Plot the monthly prior for model SST- and SAT-derived D47 values, model SSS-derived carbonate d18O values and precipitation with propagated uncertaintyΒΆ
InΒ [13]:
# Plot monthly prior distribution
fig, axes = plt.subplots(2, 2, figsize=(15, 12)) # Adjust the figure to have 2x2 grid
# Plot the prior distribution for SST
axes[0, 0].plot(months_scale, mu_prior_SST_D47_monthly, label='Prior SST Mean', color='b', marker='o')
axes[0, 0].fill_between(months_scale,
mu_prior_SST_D47_monthly - stats.t.ppf(1 - 0.025, n_gridcells_monthly) * std_prior_SST_D47_monthly / np.sqrt(n_gridcells_monthly),
mu_prior_SST_D47_monthly + stats.t.ppf(1 - 0.025, n_gridcells_monthly) * std_prior_SST_D47_monthly / np.sqrt(n_gridcells_monthly),
color='b', alpha=0.2, label='95% Confidence Interval')
axes[0, 0].set_xticks(months_scale)
axes[0, 0].set_xticklabels(month_names, rotation=45, ha="right")
axes[0, 0].set_title('Prior Mean and 95% Confidence Interval for Monthly SST D47 values')
axes[0, 0].set_xlabel('Month')
axes[0, 0].set_ylabel('D47 value')
axes[0, 0].legend()
axes[0, 0].grid(True)
# Plot the prior distribution for SAT
axes[0, 1].plot(months_scale, mu_prior_SAT_D47_monthly, label='Prior SAT Mean', color='r', marker='o')
axes[0, 1].fill_between(months_scale,
mu_prior_SAT_D47_monthly - stats.t.ppf(1 - 0.025, n_gridcells_monthly) * std_prior_SAT_D47_monthly / np.sqrt(n_gridcells_monthly),
mu_prior_SAT_D47_monthly + stats.t.ppf(1 - 0.025, n_gridcells_monthly) * std_prior_SAT_D47_monthly / np.sqrt(n_gridcells_monthly),
color='r', alpha=0.2, label='95% Confidence Interval')
axes[0, 1].set_xticks(months_scale)
axes[0, 1].set_xticklabels(month_names, rotation=45, ha="right")
axes[0, 1].set_title('Prior Mean and 95% Confidence Interval for Monthly SAT D47 values')
axes[0, 1].set_xlabel('Month')
axes[0, 1].set_ylabel('D47 value')
axes[0, 1].legend()
axes[0, 1].grid(True)
# Plot the prior distribution for d18Oc
axes[1, 0].plot(months_scale, mu_prior_d18Oc_monthly, label='Prior d18Oc Mean', color='purple', marker='o')
axes[1, 0].fill_between(months_scale,
mu_prior_d18Oc_monthly - stats.t.ppf(1 - 0.025, n_gridcells_monthly) * std_prior_d18Oc_monthly / np.sqrt(n_gridcells_monthly),
mu_prior_d18Oc_monthly + stats.t.ppf(1 - 0.025, n_gridcells_monthly) * std_prior_d18Oc_monthly / np.sqrt(n_gridcells_monthly),
color='purple', alpha=0.2, label='95% Confidence Interval')
axes[1, 0].set_xticks(months_scale)
axes[1, 0].set_xticklabels(month_names, rotation=45, ha="right")
axes[1, 0].set_title('Prior Mean and 95% Confidence Interval for Monthly d18Oc values')
axes[1, 0].set_xlabel('Month')
axes[1, 0].set_ylabel('d18Oc value')
axes[1, 0].legend()
axes[1, 0].grid(True)
# Plot the prior distribution for precipitation
axes[1, 1].plot(months_scale, mu_prior_precip_monthly, label='Prior Precipitation Mean', color='teal', marker='o')
axes[1, 1].fill_between(months_scale,
mu_prior_precip_monthly - stats.t.ppf(1 - 0.025, n_gridcells_monthly) * std_prior_precip_monthly / np.sqrt(n_gridcells_monthly),
mu_prior_precip_monthly + stats.t.ppf(1 - 0.025, n_gridcells_monthly) * std_prior_precip_monthly / np.sqrt(n_gridcells_monthly),
color='teal', alpha=0.2, label='95% Confidence Interval')
axes[1, 1].set_xticks(months_scale)
axes[1, 1].set_xticklabels(month_names, rotation=45, ha="right")
axes[1, 1].set_title('Prior Mean and 95% Confidence Interval for Monthly Precipitation values')
axes[1, 1].set_xlabel('Month')
axes[1, 1].set_ylabel('Precipitation (mm/day)')
axes[1, 1].legend()
axes[1, 1].grid(True)
# Update the layout and show the plot
plt.tight_layout()
plt.show()
Calculate the monthly covariance matrix for D47 values of SST and SAT, d18Oc and precipitationΒΆ
InΒ [14]:
# Define column names for SAT, SST, d18Oc, and precipitation
SAT_D47_columns_monthly = [f"{month}_SAT_D47" for month in months]
SST_D47_columns_monthly = [f"{month}_SST_D47" for month in months]
d18Oc_columns_monthly = [f"{month}_d18Oc" for month in months]
precip_columns_monthly = [f"{month}_precip" for month in months]
# Extract the relevant columns for SAT, SST D47, d18Oc, and precipitation
SAT_D47_columns_monthly = [f"{month}_SAT_D47" for month in months]
SST_D47_columns_monthly = [f"{month}_SST_D47" for month in months]
d18Oc_columns_monthly = [f"{month}_d18Oc" for month in months]
precip_columns_monthly = [f"{month}_precip" for month in months]
# Combine the relevant columns into a single dataframe
combined_data_monthly = IPCC_Atlas[SAT_D47_columns_monthly + SST_D47_columns_monthly + d18Oc_columns_monthly + precip_columns_monthly]
# Calculate the covariance matrix for the combined data
cov_combined_monthly = np.cov(combined_data_monthly.dropna(), rowvar=False)
# Plot the heatmap of the raw combined covariance matrix
plt.figure(figsize=(12, 10))
sns.heatmap(
cov_combined_monthly, # Use the raw covariance matrix
annot=False,
fmt=".2f",
cmap="coolwarm",
center=0,
xticklabels=SAT_D47_columns_monthly + SST_D47_columns_monthly + d18Oc_columns_monthly + precip_columns_monthly,
yticklabels=SAT_D47_columns_monthly + SST_D47_columns_monthly + d18Oc_columns_monthly + precip_columns_monthly
)
# Add titles to the axes per parameter
plt.axvline(x=len(SAT_D47_columns_monthly), color='black', linestyle='--', linewidth=1)
plt.axvline(x=len(SAT_D47_columns_monthly) + len(SST_D47_columns_monthly), color='black', linestyle='--', linewidth=1)
plt.axvline(x=len(SAT_D47_columns_monthly) + len(SST_D47_columns_monthly) + len(d18Oc_columns_monthly), color='black', linestyle='--', linewidth=1)
plt.axhline(y=len(SAT_D47_columns_monthly), color='black', linestyle='--', linewidth=1)
plt.axhline(y=len(SAT_D47_columns_monthly) + len(SST_D47_columns_monthly), color='black', linestyle='--', linewidth=1)
plt.axhline(y=len(SAT_D47_columns_monthly) + len(SST_D47_columns_monthly) + len(d18Oc_columns_monthly), color='black', linestyle='--', linewidth=1)
# Add parameter labels
plt.text(len(SAT_D47_columns_monthly) / 2, -2, 'D47 value from SAT', ha='center', va='center', fontsize=10)
plt.text(len(SAT_D47_columns_monthly) + len(SST_D47_columns_monthly) / 2, -2, 'D47 value from SST', ha='center', va='center', fontsize=10)
plt.text(len(SAT_D47_columns_monthly) + len(SST_D47_columns_monthly) + len(d18Oc_columns_monthly) / 2, -2, 'd18Oc', ha='center', va='center', fontsize=10)
plt.text(len(SAT_D47_columns_monthly) + len(SST_D47_columns_monthly) + len(d18Oc_columns_monthly) + len(precip_columns_monthly) / 2, -2, 'Precipitation', ha='center', va='center', fontsize=10)
plt.text(-2, len(SAT_D47_columns_monthly) / 2, 'D47 value from SAT', ha='center', va='center', rotation=90, fontsize=10)
plt.text(-2, len(SAT_D47_columns_monthly) + len(SST_D47_columns_monthly) / 2, 'D47 value from SST', ha='center', va='center', rotation=90, fontsize=10)
plt.text(-2, len(SAT_D47_columns_monthly) + len(SST_D47_columns_monthly) + len(d18Oc_columns_monthly) / 2, 'd18Oc', ha='center', va='center', rotation=90, fontsize=10)
plt.text(-2, len(SAT_D47_columns_monthly) + len(SST_D47_columns_monthly) + len(d18Oc_columns_monthly) + len(precip_columns_monthly) / 2, 'Precipitation', ha='center', va='center', rotation=90, fontsize=10)
plt.title("Raw Combined Covariance Matrix")
plt.show()
Plot normalized monthly covariance matrix between D47 values of SST and SAT, d18Oc and precipitationΒΆ
InΒ [15]:
# Normalize each submatrix independently for better visualization
def normalize_matrix(matrix):
min_val = np.min(matrix)
max_val = np.max(matrix)
return (matrix - min_val) / (max_val - min_val)
# Extract the relevant columns for SAT, SST D47, d18Oc, and precipitation
SAT_D47_columns_monthly = [f"{month}_SAT_D47" for month in months]
SST_D47_columns_monthly = [f"{month}_SST_D47" for month in months]
d18Oc_columns_monthly = [f"{month}_d18Oc" for month in months]
precip_columns_monthly = [f"{month}_precip" for month in months]
# Combine the relevant columns into a single dataframe
combined_data_monthly = IPCC_Atlas[SAT_D47_columns_monthly + SST_D47_columns_monthly + d18Oc_columns_monthly + precip_columns_monthly]
# Calculate the covariance matrix for the combined data
cov_combined_monthly = np.cov(combined_data_monthly.dropna(), rowvar=False)
# Extract the covariance matrices for SAT D47, SST D47, d18Oc, and precipitation
cov_SAT_D47_monthly = cov_combined_monthly[:len(months), :len(months)]
cov_SST_D47_monthly = cov_combined_monthly[len(months):2*len(months), len(months):2*len(months)]
cov_d18Oc_monthly = cov_combined_monthly[2*len(months):3*len(months), 2*len(months):3*len(months)]
cov_precip_monthly = cov_combined_monthly[3*len(months):, 3*len(months):]
# Extract the cross-covariance matrices
cross_cov_SAT_SST_D47_monthly = cov_combined_monthly[:len(months), len(months):2*len(months)]
cross_cov_SAT_d18Oc_monthly = cov_combined_monthly[:len(months), 2*len(months):3*len(months)]
cross_cov_SAT_precip_monthly = cov_combined_monthly[:len(months), 3*len(months):]
cross_cov_SST_d18Oc_monthly = cov_combined_monthly[len(months):2*len(months), 2*len(months):3*len(months)]
cross_cov_SST_precip_monthly = cov_combined_monthly[len(months):2*len(months), 3*len(months):]
cross_cov_d18Oc_precip_monthly = cov_combined_monthly[2*len(months):3*len(months), 3*len(months):]
# Normalize each submatrix
normalized_cov_SAT_D47_monthly = normalize_matrix(cov_SAT_D47_monthly)
normalized_cov_SST_D47_monthly = normalize_matrix(cov_SST_D47_monthly)
normalized_cov_d18Oc_monthly = normalize_matrix(cov_d18Oc_monthly)
normalized_cov_precip_monthly = normalize_matrix(cov_precip_monthly)
# Normalize each cross-covariance matrix
normalized_cross_cov_SAT_SST_D47_monthly = normalize_matrix(cross_cov_SAT_SST_D47_monthly)
normalized_cross_cov_SAT_d18Oc_monthly = normalize_matrix(cross_cov_SAT_d18Oc_monthly)
normalized_cross_cov_SAT_precip_monthly = normalize_matrix(cross_cov_SAT_precip_monthly)
normalized_cross_cov_SST_d18Oc_monthly = normalize_matrix(cross_cov_SST_d18Oc_monthly)
normalized_cross_cov_SST_precip_monthly = normalize_matrix(cross_cov_SST_precip_monthly)
normalized_cross_cov_d18Oc_precip_monthly = normalize_matrix(cross_cov_d18Oc_precip_monthly)
# Combine the normalized submatrices into a single normalized covariance matrix
normalized_cov_combined_monthly = np.block([
[normalized_cov_SAT_D47_monthly, normalized_cross_cov_SAT_SST_D47_monthly, normalized_cross_cov_SAT_d18Oc_monthly, normalized_cross_cov_SAT_precip_monthly],
[normalized_cross_cov_SAT_SST_D47_monthly.T, normalized_cov_SST_D47_monthly, normalized_cross_cov_SST_d18Oc_monthly, normalized_cross_cov_SST_precip_monthly],
[normalized_cross_cov_SAT_d18Oc_monthly.T, normalized_cross_cov_SST_d18Oc_monthly.T, normalized_cov_d18Oc_monthly, normalized_cross_cov_d18Oc_precip_monthly],
[normalized_cross_cov_SAT_precip_monthly.T, normalized_cross_cov_SST_precip_monthly.T, normalized_cross_cov_d18Oc_precip_monthly.T, normalized_cov_precip_monthly]
])
# Plot the heatmap of the normalized combined covariance matrix
plt.figure(figsize=(12, 10))
sns.heatmap(
normalized_cov_combined_monthly,
annot=False,
fmt=".2f",
cmap="coolwarm",
center=0,
xticklabels=SAT_D47_columns_monthly + SST_D47_columns_monthly + d18Oc_columns_monthly + precip_columns_monthly,
yticklabels=SAT_D47_columns_monthly + SST_D47_columns_monthly + d18Oc_columns_monthly + precip_columns_monthly
)
# Add titles to the axes per parameter
plt.axvline(x=len(SAT_D47_columns_monthly), color='black', linestyle='--', linewidth=1)
plt.axvline(x=len(SAT_D47_columns_monthly) + len(SST_D47_columns_monthly), color='black', linestyle='--', linewidth=1)
plt.axvline(x=len(SAT_D47_columns_monthly) + len(SST_D47_columns_monthly) + len(d18Oc_columns_monthly), color='black', linestyle='--', linewidth=1)
plt.axhline(y=len(SAT_D47_columns_monthly), color='black', linestyle='--', linewidth=1)
plt.axhline(y=len(SAT_D47_columns_monthly) + len(SST_D47_columns_monthly), color='black', linestyle='--', linewidth=1)
plt.axhline(y=len(SAT_D47_columns_monthly) + len(SST_D47_columns_monthly) + len(d18Oc_columns_monthly), color='black', linestyle='--', linewidth=1)
# Add parameter labels
plt.text(len(SAT_D47_columns_monthly) / 2, -2, 'D47 value from SAT', ha='center', va='center', fontsize=10)
plt.text(len(SAT_D47_columns_monthly) + len(SST_D47_columns_monthly) / 2, -2, 'D47 value from SST', ha='center', va='center', fontsize=10)
plt.text(len(SAT_D47_columns_monthly) + len(SST_D47_columns_monthly) + len(d18Oc_columns_monthly) / 2, -2, 'd18Oc', ha='center', va='center', fontsize=10)
plt.text(len(SAT_D47_columns_monthly) + len(SST_D47_columns_monthly) + len(d18Oc_columns_monthly) + len(precip_columns_monthly) / 2, -2, 'Precipitation', ha='center', va='center', fontsize=10)
plt.text(-7, len(SAT_D47_columns_monthly) / 2, 'D47 value from SAT', ha='center', va='center', rotation=90, fontsize=10)
plt.text(-7, len(SAT_D47_columns_monthly) + len(SST_D47_columns_monthly) / 2, 'D47 value from SST', ha='center', va='center', rotation=90, fontsize=10)
plt.text(-7, len(SAT_D47_columns_monthly) + len(SST_D47_columns_monthly) + len(d18Oc_columns_monthly) / 2, 'd18Oc', ha='center', va='center', rotation=90, fontsize=10)
plt.text(-7, len(SAT_D47_columns_monthly) + len(SST_D47_columns_monthly) + len(d18Oc_columns_monthly) + len(precip_columns_monthly) / 2, 'Precipitation', ha='center', va='center', rotation=90, fontsize=10)
plt.title("Normalized Combined Covariance Matrix")
plt.show()
Create combined monthly state vector in D47 and d18Oc domainΒΆ
InΒ [16]:
# Combine the prior means of D47 and SAT into a single state vector
mu_prior_monthly_combined = np.concatenate((mu_prior_SST_D47_monthly, mu_prior_SAT_D47_monthly, mu_prior_d18Oc_monthly, mu_prior_precip_monthly))
# Combine the covariance matrices of D47 values of SST and SAT, including the cross-covariance
cov_prior_monthly_combined = cov_combined_monthly.copy()
PRIOR - SEASONALΒΆ
Seasonal model data (convert the IPCC_Atlas data to seasonal means)ΒΆ
InΒ [17]:
# Define the seasons
seasons = {
"winter": ["dc", "ja", "fb"],
"spring": ["mr", "ar", "my"],
"summer": ["jn", "jl", "ag"],
"autumn": ["sp", "ot", "nv"],
}
# Stack monthly columns to create seasonal dataframes
# Initialize dictionaries to store seasonal data
IPCC_Atlas_seasonal_dict = {}
# Identify the columns to process (all except the modelname column)
columns_to_process = [col for col in IPCC_Atlas.columns if any(suffix in col for suffix in [
'_SST', '_SAT', '_SST_D47', '_SST_D47_SE', '_SAT_D47', '_SAT_D47_SE',
'_SSS', '_d18Oc', '_precip'
])]
# Process each season
for season, months in seasons.items(): # Iterate over the seasons and corresponding months
for col in columns_to_process: # Iterate over the columns to process
base_col_name = col.split('_')[0] # Extract the base column name
suffix = '_'.join(col.split('_')[1:]) # Extract the suffix
if base_col_name in months: # Check if the column corresponds to the current season
season_col_name = f"{season}_{suffix}" # Create the new column name
if season_col_name not in IPCC_Atlas_seasonal_dict: # Check if the new column name already exists in the seasonal data
IPCC_Atlas_seasonal_dict[season_col_name] = [] # If not, initialize a new column in the seasonal data means
IPCC_Atlas_seasonal_dict[season_col_name].append(IPCC_Atlas[col])
# Combine the seasonal data into a single dataframe
IPCC_Atlas_seasonal = pd.DataFrame()
for season_col_name, data in IPCC_Atlas_seasonal_dict.items():
# Concatenate the data for each season and reshape it properly
concatenated_data = pd.concat(data, axis=0).reset_index(drop=True)
IPCC_Atlas_seasonal[season_col_name] = concatenated_data
# Add model names
IPCC_Atlas_seasonal["lat"] = np.tile(IPCC_Atlas["lat"].values, 3) # Repeat the model names for each season
IPCC_Atlas_seasonal["lon"] = np.tile(IPCC_Atlas["lon"].values, 3) # Repeat the model names for each season
# Display the new seasonal DataFrame
D47_columns_seasonal = [col for col in IPCC_Atlas_seasonal.columns if col.endswith('_D47')]
D47_se_columns_seasonal = [col for col in IPCC_Atlas_seasonal.columns if '_D47_SE' in col]
SSS_columns_seasonal = [col for col in IPCC_Atlas_seasonal.columns if col.endswith('_SSS')]
d18Oc_columns_seasonal = [col for col in IPCC_Atlas_seasonal.columns if col.endswith('_d18Oc')]
precip_columns_seasonal = [col for col in IPCC_Atlas_seasonal.columns if col.endswith('_precip')]
print("Seasonal D47 values for all SST model outcomes:\n", IPCC_Atlas_seasonal[D47_columns_seasonal].head())
print("Calibration standard errors for all SST model outcomes:\n", IPCC_Atlas_seasonal[D47_se_columns_seasonal].head())
print("Seasonal SSS values for all model outcomes:\n", IPCC_Atlas_seasonal[SSS_columns_seasonal].head())
print("Seasonal d18Oc values for all model outcomes:\n", IPCC_Atlas_seasonal[d18Oc_columns_seasonal].head())
print("Seasonal precipitation values for all model outcomes:\n", IPCC_Atlas_seasonal[precip_columns_seasonal].head())
Seasonal D47 values for all SST model outcomes:
winter_SST_D47 winter_SAT_D47 spring_SST_D47 spring_SAT_D47 \
0 NaN NaN NaN NaN
1 NaN NaN NaN NaN
2 NaN NaN NaN NaN
3 0.583807 0.588737 0.584869 0.585712
4 0.583782 0.588877 0.587996 0.586618
summer_SST_D47 summer_SAT_D47 autumn_SST_D47 autumn_SAT_D47
0 NaN NaN NaN NaN
1 NaN NaN NaN NaN
2 NaN NaN NaN NaN
3 0.582139 0.588755 0.583674 0.589746
4 0.586037 0.588803 0.583404 0.589790
Calibration standard errors for all SST model outcomes:
winter_SST_D47_SE winter_SAT_D47_SE spring_SST_D47_SE spring_SAT_D47_SE \
0 NaN NaN NaN NaN
1 NaN NaN NaN NaN
2 NaN NaN NaN NaN
3 0.001043 0.001048 0.001044 0.001044
4 0.001043 0.001048 0.001047 0.001045
summer_SST_D47_SE summer_SAT_D47_SE autumn_SST_D47_SE autumn_SAT_D47_SE
0 NaN NaN NaN NaN
1 NaN NaN NaN NaN
2 NaN NaN NaN NaN
3 0.001042 0.001048 0.001043 0.001049
4 0.001045 0.001048 0.001043 0.001049
Seasonal SSS values for all model outcomes:
winter_SSS spring_SSS summer_SSS autumn_SSS
0 31.473394 31.977873 30.189136 30.546265
1 30.293614 32.084429 29.721171 30.029132
2 29.279388 32.328173 29.425948 29.038743
3 29.224964 32.912211 29.718203 28.856813
4 28.165424 33.240089 28.278848 28.199434
Seasonal d18Oc values for all model outcomes:
winter_d18Oc spring_d18Oc summer_d18Oc autumn_d18Oc
0 NaN NaN NaN NaN
1 NaN NaN NaN NaN
2 NaN NaN NaN NaN
3 -2.975600 -2.219563 -3.273769 -3.374633
4 -2.704737 -1.912361 -3.137344 -3.505681
Seasonal precipitation values for all model outcomes:
winter_precip spring_precip summer_precip autumn_precip
0 NaN NaN NaN NaN
1 NaN NaN NaN NaN
2 NaN NaN NaN NaN
3 2.590541 1.689385 12.423080 7.746123
4 2.881293 1.779216 12.493553 7.909942
Calculate the seasonal prior for model SST, SAT, SSS and precipitation with propagated uncertaintyΒΆ
InΒ [18]:
# Prior estimates from climate models (mean)
mu_prior_SAT_seasonal = np.array(IPCC_Atlas_seasonal[[f"{season}_SAT" for season in seasons]].mean(axis=0, skipna=True))
mu_prior_SST_seasonal = np.array(IPCC_Atlas_seasonal[[f"{season}_SST" for season in seasons]].mean(axis=0, skipna=True))
mu_prior_SSS_seasonal = np.array(IPCC_Atlas_seasonal[[f"{season}_SSS" for season in seasons]].mean(axis=0, skipna=True))
mu_prior_precip_seasonal = np.array(IPCC_Atlas_seasonal[[f"{season}_precip" for season in seasons]].mean(axis=0, skipna=True))
# Covariance between seasons in prior estimates from climate models (covariance matrix)
cov_prior_SAT_seasonal = np.cov(IPCC_Atlas_seasonal[[f"{season}_SAT" for season in seasons]].dropna(), rowvar=False)
cov_prior_SST_seasonal = np.cov(IPCC_Atlas_seasonal[[f"{season}_SST" for season in seasons]].dropna(), rowvar=False)
cov_prior_SSS_seasonal = np.cov(IPCC_Atlas_seasonal[[f"{season}_SSS" for season in seasons]].dropna(), rowvar=False)
cov_prior_precip_seasonal = np.cov(IPCC_Atlas_seasonal[[f"{season}_precip" for season in seasons]].dropna(), rowvar=False)
# Store copy of original prior means to keep when later updating the prior
mu_prior_SAT_seasonal_original, cov_prior_SAT_seasonal_original = mu_prior_SAT_seasonal.copy(), cov_prior_SAT_seasonal.copy()
mu_prior_SST_seasonal_original, cov_prior_SST_seasonal_original = mu_prior_SST_seasonal.copy(), cov_prior_SST_seasonal.copy()
mu_prior_SSS_seasonal_original, cov_prior_SSS_seasonal_original = mu_prior_SSS_seasonal.copy(), cov_prior_SSS_seasonal.copy()
mu_prior_precip_seasonal_original, cov_prior_precip_seasonal_original = mu_prior_precip_seasonal.copy(), cov_prior_precip_seasonal.copy()
# Extract the standard deviations (uncertainty) from the covariance matrix
std_prior_SAT_seasonal = np.sqrt(np.diag(cov_prior_SAT_seasonal))
std_prior_SST_seasonal = np.sqrt(np.diag(cov_prior_SST_seasonal))
std_prior_SSS_seasonal = np.sqrt(np.diag(cov_prior_SSS_seasonal))
std_prior_precip_seasonal = np.sqrt(np.diag(cov_prior_precip_seasonal))
# Print the results
print("SAT Seasonal Means:", mu_prior_SAT_seasonal)
print("SAT Seasonal Std Devs:", std_prior_SAT_seasonal)
print("SST Seasonal Means:", mu_prior_SST_seasonal)
print("SST Seasonal Std Devs:", std_prior_SST_seasonal)
print("SSS Seasonal Means:", mu_prior_SSS_seasonal)
print("SSS Seasonal Std Devs:", std_prior_SSS_seasonal)
print("Precipitation Seasonal Means:", mu_prior_precip_seasonal)
print("Precipitation Seasonal Std Devs:", std_prior_precip_seasonal)
SAT Seasonal Means: [26.26354562 26.79566898 26.52403169 26.36912953] SAT Seasonal Std Devs: [0.48353899 0.47684737 0.59702245 0.66270196] SST Seasonal Means: [27.54933547 27.18525559 28.60013027 29.08157766] SST Seasonal Std Devs: [0.87357802 1.13352575 0.5748445 0.48432974] SSS Seasonal Means: [32.9804294 34.09628244 32.23497744 31.58986653] SSS Seasonal Std Devs: [2.69635668 2.07310351 2.82074275 2.85149091] Precipitation Seasonal Means: [ 1.5836546 2.97928946 11.53320424 10.27162417] Precipitation Seasonal Std Devs: [1.19706106 2.51465787 1.21440914 1.94491224]
Plot the seasonal prior for model SST, SAT, SSS and measured precipitationΒΆ
InΒ [19]:
# Define the seasons, number of models, and scale for the x-axis
seasons = ["winter", "spring", "summer", "autumn"]
n_gridcells_seasonal = len(IPCC_Atlas["lat"]) # Find the total number of models
seasons_scale = np.arange(len(seasons)) + 1 # Create seasonal scale
# Create a 1x2 plotting grid
fig, axes = plt.subplots(2, 1, figsize=(10, 10))
# Panel 1: Plot the prior distribution for SST and SAT
axes[0].plot(seasons_scale, mu_prior_SST_seasonal, label='Prior SST Mean', marker='o', color='b')
axes[0].fill_between(
seasons_scale,
mu_prior_SST_seasonal - stats.t.ppf(1 - 0.025, n_gridcells_seasonal) * std_prior_SST_seasonal / np.sqrt(n_gridcells_seasonal),
mu_prior_SST_seasonal + stats.t.ppf(1 - 0.025, n_gridcells_seasonal) * std_prior_SST_seasonal / np.sqrt(n_gridcells_seasonal),
alpha=0.2, color='b', label='SST 95% CI'
)
axes[0].plot(seasons_scale, mu_prior_SAT_seasonal, label='Prior SAT Mean', marker='o', color='r')
axes[0].fill_between(
seasons_scale,
mu_prior_SAT_seasonal - stats.t.ppf(1 - 0.025, n_gridcells_seasonal) * std_prior_SAT_seasonal / np.sqrt(n_gridcells_seasonal),
mu_prior_SAT_seasonal + stats.t.ppf(1 - 0.025, n_gridcells_seasonal) * std_prior_SAT_seasonal / np.sqrt(n_gridcells_seasonal),
alpha=0.2, color='r', label='SAT 95% CI'
)
axes[0].set_title('Prior Mean and 95% Confidence Interval for Seasonal SST & SAT')
axes[0].set_xlabel('Season')
axes[0].set_ylabel('Temperature (Β°C)')
axes[0].set_xticks(seasons_scale)
axes[0].set_xticklabels(seasons)
axes[0].legend()
axes[0].grid(True)
# Panel 2: Plot the prior distribution for SSS and precipitation
axes[1].plot(seasons_scale, mu_prior_SSS_seasonal, label='Prior SSS Mean', marker='o', color='g')
ax2 = axes[1].twinx() # Create a secondary y-axis for precipitation
ax2.plot(seasons_scale, mu_prior_precip_seasonal, label='Prior Precipitation Mean', marker='o', color='purple')
# Add 95% confidence intervals for SSS
axes[1].fill_between(
seasons_scale,
mu_prior_SSS_seasonal - stats.t.ppf(1 - 0.025, n_gridcells_seasonal) * std_prior_SSS_seasonal / np.sqrt(n_gridcells_seasonal),
mu_prior_SSS_seasonal + stats.t.ppf(1 - 0.025, n_gridcells_seasonal) * std_prior_SSS_seasonal / np.sqrt(n_gridcells_seasonal),
alpha=0.2, color='g', label='SSS 95% CI'
)
# Add 95% confidence intervals for precipitation
ax2.fill_between(
seasons_scale,
mu_prior_precip_seasonal - stats.t.ppf(1 - 0.025, n_gridcells_seasonal) * std_prior_precip_seasonal / np.sqrt(n_gridcells_seasonal),
mu_prior_precip_seasonal + stats.t.ppf(1 - 0.025, n_gridcells_seasonal) * std_prior_precip_seasonal / np.sqrt(n_gridcells_seasonal),
alpha=0.2, color='purple', label='Precipitation 95% CI'
)
axes[1].set_title('Prior Mean and 95% Confidence Interval for Seasonal SSS & Precipitation')
axes[1].set_xlabel('Season')
axes[1].set_ylabel('SSS (psu)', color='g')
ax2.set_ylabel('Precipitation (mm/day)', color='purple')
axes[1].set_xticks(seasons_scale)
axes[1].set_xticklabels(seasons)
axes[1].legend(loc='upper left')
ax2.legend(loc='upper right')
axes[1].grid(True)
# Adjust layout and show the plot
plt.tight_layout()
plt.show()
Calculate the seasonal prior for model SST and SAT-derived D47 values with propagated uncertaintyΒΆ
InΒ [20]:
# Set the weights of the data based on the standard errors
weights_seasonal_SST_D47 = 1 / IPCC_Atlas_seasonal[[f"{season}_SST_D47_SE" for season in seasons]] ** 2
weights_seasonal_SAT_D47 = 1 / IPCC_Atlas_seasonal[[f"{season}_SAT_D47_SE" for season in seasons]] ** 2
# Change the column suffixes from "_D47_SE" to "_D47" in weights_seasonal to match the headers of the D47 matrix later for multiplication
weights_seasonal_SST_D47.columns = [col.replace('_SST_D47_SE', '_SST_D47') for col in weights_seasonal_SST_D47.columns]
weights_seasonal_SAT_D47.columns = [col.replace('_SAT_D47_SE', '_SAT_D47') for col in weights_seasonal_SAT_D47.columns]
# Prior D47 estimates from climate models (weighted mean)
mu_prior_SST_D47_seasonal = np.array((IPCC_Atlas_seasonal[[f"{season}_SST_D47" for season in seasons]] * weights_seasonal_SST_D47).sum(axis = 0, skipna = True) / weights_seasonal_SST_D47.sum(axis = 0, skipna = True)) # Calculate weighted seasonal mean D47 values and convert to numpy array
mu_prior_SAT_D47_seasonal = np.array((IPCC_Atlas_seasonal[[f"{season}_SAT_D47" for season in seasons]] * weights_seasonal_SAT_D47).sum(axis = 0, skipna = True) / weights_seasonal_SAT_D47.sum(axis = 0, skipna = True)) # Calculate weighted seasonal mean D47 values and convert to numpy array
# Calculate simple (unweighted) mean for monthly d18Oc values
mu_prior_d18Oc_seasonal = np.array(IPCC_Atlas_seasonal[[f"{season}_d18Oc" for season in seasons]].mean(axis=0, skipna=True))
mu_prior_precip_seasonal = np.array(IPCC_Atlas_seasonal[[f"{season}_precip" for season in seasons]].mean(axis=0, skipna=True))
# Decompose variance within and between model outcomes
model_variances_SST_D47 = IPCC_Atlas_seasonal[[f"{season}_SST_D47" for season in seasons]].var(axis = 0, ddof = 1) # Compute variance across models
model_variances_SAT_D47 = IPCC_Atlas_seasonal[[f"{season}_SAT_D47" for season in seasons]].var(axis = 0, ddof = 1) # Compute variance across models
measurement_variances_SST_D47 = (IPCC_Atlas_seasonal[[f"{season}_SST_D47_SE" for season in seasons]] ** 2).mean(axis = 0, skipna = True) # Compute variance on measurements
measurement_variances_SAT_D47 = (IPCC_Atlas_seasonal[[f"{season}_SAT_D47_SE" for season in seasons]] ** 2).mean(axis = 0, skipna = True) # Compute variance on measurements
# Covariance between seasons in prior D47 estimates from climate models (weighted covariance matrix)
cov_raw_seasonal_SST_D47 = np.cov(IPCC_Atlas_seasonal[[f"{season}_SST_D47" for season in seasons]].dropna(), rowvar = False) # Compute the covariance matrix for the raw data (without measurement uncertainty)
cov_raw_seasonal_SAT_D47 = np.cov(IPCC_Atlas_seasonal[[f"{season}_SAT_D47" for season in seasons]].dropna(), rowvar = False) # Compute the covariance matrix for the raw data (without measurement uncertainty)
cov_raw_seasonal_d18Oc = np.cov(IPCC_Atlas_seasonal[[f"{season}_d18Oc" for season in seasons]].dropna(), rowvar = False) # Compute the covariance matrix for the raw data (without measurement uncertainty)
cov_raw_seasonal_precip = np.cov(IPCC_Atlas_seasonal[[f"{season}_precip" for season in seasons]].dropna(), rowvar = False) # Compute the covariance matrix for the raw data (without measurement uncertainty)
cov_prior_SST_D47_seasonal = cov_raw_seasonal_SST_D47.copy() # Copy covariance matrix to add uncertainty coming from the measurements
cov_prior_SAT_D47_seasonal = cov_raw_seasonal_SAT_D47.copy() # Copy covariance matrix to add uncertainty coming from the measurements
cov_prior_d18Oc_seasonal = cov_raw_seasonal_d18Oc.copy() # Copy covariance matrix to add uncertainty coming from the measurements
cov_prior_precip_seasonal = cov_raw_seasonal_precip.copy() # Copy covariance matrix to add uncertainty coming from the measurements
np.fill_diagonal(cov_prior_SST_D47_seasonal, np.diagonal(cov_raw_seasonal_SST_D47) + measurement_variances_SST_D47) # Add diagonal terms for measurement uncertainties (which have no covariance between models)
np.fill_diagonal(cov_prior_SAT_D47_seasonal, np.diagonal(cov_raw_seasonal_SAT_D47) + measurement_variances_SAT_D47) # Add diagonal terms for measurement uncertainties (which have no covariance between models)
# Store copy of original prior means to keep when later updating the prior
mu_prior_SST_D47_seasonal_original, cov_prior_SST_D47_seasonal_original = mu_prior_SST_D47_seasonal.copy(), cov_prior_SST_D47_seasonal.copy()
mu_prior_SAT_D47_seasonal_original, cov_prior_SAT_D47_seasonal_original = mu_prior_SAT_D47_seasonal.copy(), cov_prior_SAT_D47_seasonal.copy()
mu_prior_d18Oc_seasonal_original, cov_prior_d18Oc_seasonal_original = mu_prior_d18Oc_seasonal.copy(), cov_raw_seasonal_d18Oc.copy()
mu_prior_precip_seasonal_original, cov_prior_precip_seasonal_original = mu_prior_precip_seasonal.copy(), cov_raw_seasonal_precip.copy()
# Extract the standard deviations (uncertainty) from the covariance matrix
std_prior_SST_D47_seasonal = np.sqrt(np.diag(cov_prior_SST_D47_seasonal))
std_prior_SAT_D47_seasonal = np.sqrt(np.diag(cov_prior_SAT_D47_seasonal))
std_prior_d18Oc_seasonal = np.sqrt(np.diag(cov_prior_d18Oc_seasonal))
std_prior_precip_seasonal = np.sqrt(np.diag(cov_prior_precip_seasonal))
print("mu_prior_SST_D47_seasonal:", mu_prior_SST_D47_seasonal)
print("std_prior_SST_D47_seasonal:", std_prior_SST_D47_seasonal)
print("mu_prior_SAT_D47_seasonal:", mu_prior_SAT_D47_seasonal)
print("std_prior_SAT_D47_seasonal:", std_prior_SAT_D47_seasonal)
print("mu_prior_d18Oc_seasonal:", mu_prior_d18Oc_seasonal)
print("std_prior_d18Oc_seasonal:", std_prior_d18Oc_seasonal)
print("mu_prior_precip_seasonal:", mu_prior_precip_seasonal)
print("std_prior_precip_seasonal:", std_prior_precip_seasonal)
mu_prior_SST_D47_seasonal: [0.58581025 0.58687706 0.58274323 0.58134767] std_prior_SST_D47_seasonal: [0.00277616 0.00351338 0.00197095 0.00174785] mu_prior_SAT_D47_seasonal: [0.58961097 0.58803244 0.58883642 0.58929545] std_prior_SAT_D47_seasonal: [0.00178228 0.00175895 0.00205975 0.00223382] mu_prior_d18Oc_seasonal: [-2.05926297 -1.77569652 -2.48786265 -2.74257046] std_prior_d18Oc_seasonal: [0.66384694 0.44756316 0.64969896 0.63483524] mu_prior_precip_seasonal: [ 1.5836546 2.97928946 11.53320424 10.27162417] std_prior_precip_seasonal: [1.19706106 2.51465787 1.21440914 1.94491224]
Plot the seasonal prior for model SST- and SAT-derived D47 values, d18Oc values and precipitation with propagated uncertaintyΒΆ
InΒ [21]:
# Define the seasons, number of models, and scale for the x-axis
seasons = ["winter", "spring", "summer", "autumn"]
n_gridcells_seasonal = len(IPCC_Atlas["lat"]) # Find the total number of models (use monthly data because seasonal data has this column duplicated 3 times)
seasons_scale = np.arange(len(seasons)) + 1 # Create seasonal scale
# Create a 2x2 plotting grid
fig, axes = plt.subplots(2, 2, figsize=(15, 12))
# Panel 1: Plot the prior distribution for SST D47 values
axes[0, 0].plot(seasons_scale, mu_prior_SST_D47_seasonal[:len(seasons)], label='Prior SST D47 Mean', marker='o', color='b')
axes[0, 0].fill_between(
seasons_scale,
mu_prior_SST_D47_seasonal[:len(seasons)] - stats.t.ppf(1 - 0.025, n_gridcells_seasonal) * std_prior_SST_D47_seasonal / np.sqrt(n_gridcells_seasonal),
mu_prior_SST_D47_seasonal[:len(seasons)] + stats.t.ppf(1 - 0.025, n_gridcells_seasonal) * std_prior_SST_D47_seasonal / np.sqrt(n_gridcells_seasonal),
alpha=0.2, color='b', label='95% Confidence Interval'
)
axes[0, 0].set_title('Prior Mean and 95% Confidence Interval for Seasonal SST D47 Values')
axes[0, 0].set_xlabel('Season')
axes[0, 0].set_ylabel('D47 Value')
axes[0, 0].set_xticks(seasons_scale)
axes[0, 0].set_xticklabels(seasons)
axes[0, 0].legend()
axes[0, 0].grid(True)
# Panel 2: Plot the prior distribution for SAT D47 values
axes[0, 1].plot(seasons_scale, mu_prior_SAT_D47_seasonal[:len(seasons)], label='Prior SAT D47 Mean', marker='o', color='r')
axes[0, 1].fill_between(
seasons_scale,
mu_prior_SAT_D47_seasonal[:len(seasons)] - stats.t.ppf(1 - 0.025, n_gridcells_seasonal) * std_prior_SAT_D47_seasonal / np.sqrt(n_gridcells_seasonal),
mu_prior_SAT_D47_seasonal[:len(seasons)] + stats.t.ppf(1 - 0.025, n_gridcells_seasonal) * std_prior_SAT_D47_seasonal / np.sqrt(n_gridcells_seasonal),
alpha=0.2, color='r', label='95% Confidence Interval'
)
axes[0, 1].set_title('Prior Mean and 95% Confidence Interval for Seasonal SAT D47 Values')
axes[0, 1].set_xlabel('Season')
axes[0, 1].set_ylabel('D47 Value')
axes[0, 1].set_xticks(seasons_scale)
axes[0, 1].set_xticklabels(seasons)
axes[0, 1].legend()
axes[0, 1].grid(True)
# Panel 3: Plot the prior distribution for d18Oc
axes[1, 0].plot(seasons_scale, mu_prior_d18Oc_seasonal, label='Prior d18Oc Mean', marker='o', color='g')
axes[1, 0].fill_between(
seasons_scale,
mu_prior_d18Oc_seasonal - stats.t.ppf(1 - 0.025, n_gridcells_seasonal) * std_prior_d18Oc_seasonal / np.sqrt(n_gridcells_seasonal),
mu_prior_d18Oc_seasonal + stats.t.ppf(1 - 0.025, n_gridcells_seasonal) * std_prior_d18Oc_seasonal / np.sqrt(n_gridcells_seasonal),
alpha=0.2, color='g', label='95% Confidence Interval'
)
axes[1, 0].set_title('Prior Mean and 95% Confidence Interval for Seasonal d18Oc Values')
axes[1, 0].set_xlabel('Season')
axes[1, 0].set_ylabel('d18Oc Value')
axes[1, 0].set_xticks(seasons_scale)
axes[1, 0].set_xticklabels(seasons)
axes[1, 0].legend()
axes[1, 0].grid(True)
# Panel 4: Plot the prior distribution for precipitation
axes[1, 1].plot(seasons_scale, mu_prior_precip_seasonal, label='Prior Precipitation Mean', marker='o', color='purple')
axes[1, 1].fill_between(
seasons_scale,
mu_prior_precip_seasonal - stats.t.ppf(1 - 0.025, n_gridcells_seasonal) * std_prior_precip_seasonal / np.sqrt(n_gridcells_seasonal),
mu_prior_precip_seasonal + stats.t.ppf(1 - 0.025, n_gridcells_seasonal) * std_prior_precip_seasonal / np.sqrt(n_gridcells_seasonal),
alpha=0.2, color='purple', label='95% Confidence Interval'
)
axes[1, 1].set_title('Prior Mean and 95% Confidence Interval for Seasonal Precipitation Values')
axes[1, 1].set_xlabel('Season')
axes[1, 1].set_ylabel('Precipitation (mm/day)')
axes[1, 1].set_xticks(seasons_scale)
axes[1, 1].set_xticklabels(seasons)
axes[1, 1].legend()
axes[1, 1].grid(True)
# Adjust layout and show the plot
plt.tight_layout()
plt.show()
Calculate the seasonal covariance matrix for D47 values derived from SST and SAT values, d18Oc and precipitationΒΆ
InΒ [22]:
# Extract the relevant columns for SST, SAT D47, d18Oc, and precipitation
SST_D47_columns_seasonal = [f"{season}_SST_D47" for season in seasons]
SAT_D47_columns_seasonal = [f"{season}_SAT_D47" for season in seasons]
d18Oc_columns_seasonal = [f"{season}_d18Oc" for season in seasons]
precip_columns_seasonal = [f"{season}_precip" for season in seasons]
# Combine the relevant columns into a single dataframe
combined_data_seasonal = IPCC_Atlas_seasonal[
SST_D47_columns_seasonal + SAT_D47_columns_seasonal + d18Oc_columns_seasonal + precip_columns_seasonal
]
# Calculate the covariance matrix for the combined data
cov_combined_seasonal = np.cov(combined_data_seasonal.dropna(), rowvar=False)
# Extract the covariance matrices for each variable
cov_SST_D47_seasonal = cov_combined_seasonal[:len(seasons), :len(seasons)]
cov_SAT_D47_seasonal = cov_combined_seasonal[len(seasons):2*len(seasons), len(seasons):2*len(seasons)]
cov_d18Oc_seasonal = cov_combined_seasonal[2*len(seasons):3*len(seasons), 2*len(seasons):3*len(seasons)]
cov_precip_seasonal = cov_combined_seasonal[3*len(seasons):, 3*len(seasons):]
# Extract the cross-covariance matrices
cross_cov_SST_SAT_D47_seasonal = cov_combined_seasonal[:len(seasons), len(seasons):2*len(seasons)]
cross_cov_SST_d18Oc_seasonal = cov_combined_seasonal[:len(seasons), 2*len(seasons):3*len(seasons)]
cross_cov_SST_precip_seasonal = cov_combined_seasonal[:len(seasons), 3*len(seasons):]
cross_cov_SAT_d18Oc_seasonal = cov_combined_seasonal[len(seasons):2*len(seasons), 2*len(seasons):3*len(seasons)]
cross_cov_SAT_precip_seasonal = cov_combined_seasonal[len(seasons):2*len(seasons), 3*len(seasons):]
cross_cov_d18Oc_precip_seasonal = cov_combined_seasonal[2*len(seasons):3*len(seasons), 3*len(seasons):]
# Plot a heatmap of the combined covariance matrix
plt.figure(figsize=(12, 10))
sns.heatmap(
np.round(cov_combined_seasonal * 10**4, 1), # Scale by 10^4 for better visualization and round values
annot=False,
fmt=".2f",
cmap="coolwarm",
center=0,
xticklabels=SST_D47_columns_seasonal + SAT_D47_columns_seasonal + d18Oc_columns_seasonal + precip_columns_seasonal,
yticklabels=SST_D47_columns_seasonal + SAT_D47_columns_seasonal + d18Oc_columns_seasonal + precip_columns_seasonal
)
# Add titles to the axes per parameter
plt.axvline(x=len(SST_D47_columns_seasonal), color='black', linestyle='--', linewidth=1)
plt.axvline(x=len(SST_D47_columns_seasonal) + len(SAT_D47_columns_seasonal), color='black', linestyle='--', linewidth=1)
plt.axvline(x=len(SST_D47_columns_seasonal) + len(SAT_D47_columns_seasonal) + len(d18Oc_columns_seasonal), color='black', linestyle='--', linewidth=1)
plt.axhline(y=len(SST_D47_columns_seasonal), color='black', linestyle='--', linewidth=1)
plt.axhline(y=len(SST_D47_columns_seasonal) + len(SAT_D47_columns_seasonal), color='black', linestyle='--', linewidth=1)
plt.axhline(y=len(SST_D47_columns_seasonal) + len(SAT_D47_columns_seasonal) + len(d18Oc_columns_seasonal), color='black', linestyle='--', linewidth=1)
# Add parameter labels
plt.text(len(SST_D47_columns_seasonal) / 2, -2, 'SST D47', ha='center', va='center', fontsize=10)
plt.text(len(SST_D47_columns_seasonal) + len(SAT_D47_columns_seasonal) / 2, -2, 'SAT D47', ha='center', va='center', fontsize=10)
plt.text(len(SST_D47_columns_seasonal) + len(SAT_D47_columns_seasonal) + len(d18Oc_columns_seasonal) / 2, -2, 'd18Oc', ha='center', va='center', fontsize=10)
plt.text(len(SST_D47_columns_seasonal) + len(SAT_D47_columns_seasonal) + len(d18Oc_columns_seasonal) + len(precip_columns_seasonal) / 2, -2, 'Precipitation', ha='center', va='center', fontsize=10)
plt.title("Combined Covariance Matrix for SST D47, SAT D47, d18Oc, and Precipitation")
plt.show()
Plot normalized seasonal covariance matrix between D47 values of SST and SAT, d18Oc and precipitationΒΆ
InΒ [23]:
# Normalize each submatrix independently for better visualization
def normalize_matrix(matrix):
min_val = np.min(matrix)
max_val = np.max(matrix)
return (matrix - min_val) / (max_val - min_val)
# Extract the covariance matrices for SAT D47, SST D47, d18Oc, and precipitation
cov_SAT_D47_seasonal = cov_combined_seasonal[:len(seasons), :len(seasons)]
cov_SST_D47_seasonal = cov_combined_seasonal[len(seasons):2*len(seasons), len(seasons):2*len(seasons)]
cov_d18Oc_seasonal = cov_combined_seasonal[2*len(seasons):3*len(seasons), 2*len(seasons):3*len(seasons)]
cov_precip_seasonal = cov_combined_seasonal[3*len(seasons):, 3*len(seasons):]
# Extract the cross-covariance matrices
cross_cov_SAT_SST_D47_seasonal = cov_combined_seasonal[:len(seasons), len(seasons):2*len(seasons)]
cross_cov_SAT_d18Oc_seasonal = cov_combined_seasonal[:len(seasons), 2*len(seasons):3*len(seasons)]
cross_cov_SAT_precip_seasonal = cov_combined_seasonal[:len(seasons), 3*len(seasons):]
cross_cov_SST_d18Oc_seasonal = cov_combined_seasonal[len(seasons):2*len(seasons), 2*len(seasons):3*len(seasons)]
cross_cov_SST_precip_seasonal = cov_combined_seasonal[len(seasons):2*len(seasons), 3*len(seasons):]
cross_cov_d18Oc_precip_seasonal = cov_combined_seasonal[2*len(seasons):3*len(seasons), 3*len(seasons):]
# Normalize each submatrix
normalized_cov_SAT_D47_seasonal = normalize_matrix(cov_SAT_D47_seasonal)
normalized_cov_SST_D47_seasonal = normalize_matrix(cov_SST_D47_seasonal)
normalized_cov_d18Oc_seasonal = normalize_matrix(cov_d18Oc_seasonal)
normalized_cov_precip_seasonal = normalize_matrix(cov_precip_seasonal)
# Normalize each cross-covariance matrix
normalized_cross_cov_SAT_SST_D47_seasonal = normalize_matrix(cross_cov_SAT_SST_D47_seasonal)
normalized_cross_cov_SAT_d18Oc_seasonal = normalize_matrix(cross_cov_SAT_d18Oc_seasonal)
normalized_cross_cov_SAT_precip_seasonal = normalize_matrix(cross_cov_SAT_precip_seasonal)
normalized_cross_cov_SST_d18Oc_seasonal = normalize_matrix(cross_cov_SST_d18Oc_seasonal)
normalized_cross_cov_SST_precip_seasonal = normalize_matrix(cross_cov_SST_precip_seasonal)
normalized_cross_cov_d18Oc_precip_seasonal = normalize_matrix(cross_cov_d18Oc_precip_seasonal)
# Combine the normalized submatrices into a single normalized covariance matrix
normalized_cov_combined_seasonal = np.block([
[normalized_cov_SAT_D47_seasonal, normalized_cross_cov_SAT_SST_D47_seasonal, normalized_cross_cov_SAT_d18Oc_seasonal, normalized_cross_cov_SAT_precip_seasonal],
[normalized_cross_cov_SAT_SST_D47_seasonal.T, normalized_cov_SST_D47_seasonal, normalized_cross_cov_SST_d18Oc_seasonal, normalized_cross_cov_SST_precip_seasonal],
[normalized_cross_cov_SAT_d18Oc_seasonal.T, normalized_cross_cov_SST_d18Oc_seasonal.T, normalized_cov_d18Oc_seasonal, normalized_cross_cov_d18Oc_precip_seasonal],
[normalized_cross_cov_SAT_precip_seasonal.T, normalized_cross_cov_SST_precip_seasonal.T, normalized_cross_cov_d18Oc_precip_seasonal.T, normalized_cov_precip_seasonal]
])
# Plot the heatmap of the normalized combined covariance matrix
plt.figure(figsize=(12, 10))
sns.heatmap(
normalized_cov_combined_seasonal,
annot=False,
fmt=".2f",
cmap="coolwarm",
center=0,
xticklabels=SAT_D47_columns_seasonal + SST_D47_columns_seasonal + d18Oc_columns_seasonal + precip_columns_seasonal,
yticklabels=SAT_D47_columns_seasonal + SST_D47_columns_seasonal + d18Oc_columns_seasonal + precip_columns_seasonal
)
# Add titles to the axes per parameter
plt.axvline(x=len(SAT_D47_columns_seasonal), color='black', linestyle='--', linewidth=1)
plt.axvline(x=len(SAT_D47_columns_seasonal) + len(SST_D47_columns_seasonal), color='black', linestyle='--', linewidth=1)
plt.axvline(x=len(SAT_D47_columns_seasonal) + len(SST_D47_columns_seasonal) + len(d18Oc_columns_seasonal), color='black', linestyle='--', linewidth=1)
plt.axhline(y=len(SAT_D47_columns_seasonal), color='black', linestyle='--', linewidth=1)
plt.axhline(y=len(SAT_D47_columns_seasonal) + len(SST_D47_columns_seasonal), color='black', linestyle='--', linewidth=1)
plt.axhline(y=len(SAT_D47_columns_seasonal) + len(SST_D47_columns_seasonal) + len(d18Oc_columns_seasonal), color='black', linestyle='--', linewidth=1)
# Add parameter labels
plt.text(len(SAT_D47_columns_seasonal) / 2, -1, 'D47 value from SAT', ha='center', va='center', fontsize=10)
plt.text(len(SAT_D47_columns_seasonal) + len(SST_D47_columns_seasonal) / 2, -1, 'D47 value from SST', ha='center', va='center', fontsize=10)
plt.text(len(SAT_D47_columns_seasonal) + len(SST_D47_columns_seasonal) + len(d18Oc_columns_seasonal) / 2, -1, 'd18Oc', ha='center', va='center', fontsize=10)
plt.text(len(SAT_D47_columns_seasonal) + len(SST_D47_columns_seasonal) + len(d18Oc_columns_seasonal) + len(precip_columns_seasonal) / 2, -1, 'Precipitation', ha='center', va='center', fontsize=10)
plt.text(-3, len(SAT_D47_columns_seasonal) / 2, 'D47 value from SAT', ha='center', va='center', rotation=90, fontsize=10)
plt.text(-3, len(SAT_D47_columns_seasonal) + len(SST_D47_columns_seasonal) / 2, 'D47 value from SST', ha='center', va='center', rotation=90, fontsize=10)
plt.text(-3, len(SAT_D47_columns_seasonal) + len(SST_D47_columns_seasonal) + len(d18Oc_columns_seasonal) / 2, 'd18Oc', ha='center', va='center', rotation=90, fontsize=10)
plt.text(-3, len(SAT_D47_columns_seasonal) + len(SST_D47_columns_seasonal) + len(d18Oc_columns_seasonal) + len(precip_columns_seasonal) / 2, 'Precipitation', ha='center', va='center', rotation=90, fontsize=10)
plt.title("Normalized Combined Covariance Matrix")
plt.show()
Create combined seasonal state vectorΒΆ
InΒ [24]:
# Combine the prior means of D47 and SAT into a single state vector
mu_prior_seasonal_combined = np.concatenate((mu_prior_SST_D47_seasonal, mu_prior_SAT_D47_seasonal, mu_prior_d18Oc_seasonal, mu_prior_precip_seasonal))
# Combine the covariance matrices of D47 values of SST and SAT, d18Oc and precipitation including the cross-covariance
cov_prior_seasonal_combined = cov_combined_seasonal.copy()
Monthly and seasonal data based on age modelΒΆ
InΒ [25]:
# Load measurements and format them into a dictionary
# These are the actual individual D47 measurements and ShellChron outcomes and thus come with a time uncertainty which can be propagated.
Caldarescu_data = pd.read_csv('Modern case/Caldarescu_data.csv') # Load the data into Python and in the Jupyter environment.
Caldarescu_data_dict = Caldarescu_data.to_dict('records') # Convert to dictionary with column headers as keys
# Convert estimated day of year to month (0-11) and season (0-3), set errors to 0
for record in Caldarescu_data_dict:
doy = record.get("Estimated day of year", np.nan)
if not pd.isna(doy):
record["month_score"] = int(doy / 365 * 12) % 12 # month index 0-11
record["season_score"] = int(doy / 365 * 4) % 4 # season index 0-3
else:
record["month_score"] = np.nan
record["season_score"] = np.nan
record["Month_err"] = 0
record["Season_err"] = 0
# Print excerpt of the dictionary to check format
print(Caldarescu_data_dict[1])
{'SampName': 'M5_115', 'Specimen': 'M5', 'SampNum': 115, 'Time of year': 'Apr/Oct', 'Increment number': 2, 'Estimated date': '01/10/2005', 'Estimated day of year': 273.0, 'Final_d13Ccarb': -0.01, 'd13C_SD': 0.030214895, 'Final_d18Ocarb_VPDB': -4.32, 'd18O_SD': 0.059240652, 'D47rfac': 0.644, 'D47': 0.582, 'D47_SD': 0.039056165, 'month_score': 8, 'season_score': 2, 'Month_err': 0, 'Season_err': 0}
Prepare measurement and observation matricesΒΆ
Observations parsed as individual valuesΒΆ
Measurement matrix for individual D47 valuesΒΆ
InΒ [26]:
# Extract measurements and uncertainties from the dictionary, skipping missing values
D47_measurements_individual = [measurement["D47"] for measurement in Caldarescu_data_dict if not pd.isna(measurement["D47"])] # Extract the D47 values
d18Oc_measurements_individual = [measurement["Final_d18Ocarb_VPDB"] for measurement in Caldarescu_data_dict if not pd.isna(measurement["Final_d18Ocarb_VPDB"])] # Extract the d18Oc values
D47_measurement_uncertainties_individual = [measurement["D47_SD"] ** 2 for measurement in Caldarescu_data_dict if not pd.isna(measurement["D47"])] # Square the standard deviation to get the variance
d18Oc_measurement_uncertainties_individual = [measurement["d18O_SD"] ** 2 for measurement in Caldarescu_data_dict if not pd.isna(measurement["Final_d18Ocarb_VPDB"])] # Square the standard deviation to get the variance
# Create the measurement matrix Z
Z_individual = np.array(D47_measurements_individual + d18Oc_measurements_individual).reshape(-1, 1)
# Create the measurement uncertainty matrix R (diagonal matrix)
R_individual = np.diag(D47_measurement_uncertainties_individual + d18Oc_measurement_uncertainties_individual) # Diagonal matrix of measurement uncertainties
Monthly observation matrix for individual D47 dataΒΆ
InΒ [27]:
# Create the observation matrix H for monthly data based on individual measurements
N_measurements = len(Z_individual)
H_monthly = np.zeros((N_measurements, len(mu_prior_monthly_combined)))
# Fill the first part of the observation matrix H with ones at the positions corresponding to the D47 measurements, ignoring NaN values
for i, measurement in enumerate([m for m in Caldarescu_data_dict if not pd.isna(m["D47"])]):
time_index = int(measurement["month_score"])
H_monthly[i, time_index] = 1 # Indexing the first set of columns (1:12) for SST_D47
# Fill the second part of the observation matrix H with ones at the positions corresponding to the d18Oc measurements, ignoring NaN values
for i, measurement in enumerate([m for m in Caldarescu_data_dict if not pd.isna(m["Final_d18Ocarb_VPDB"])]):
time_index = int(measurement["month_score"])
H_monthly[i + len(D47_measurements_individual), time_index + 24] = 1 # Indexing the third column block (25:36) for d18Oc
print(H_monthly)
[[0. 0. 0. ... 0. 0. 0.] [0. 0. 0. ... 0. 0. 0.] [0. 0. 0. ... 0. 0. 0.] ... [0. 0. 0. ... 0. 0. 0.] [0. 0. 0. ... 0. 0. 0.] [0. 0. 0. ... 0. 0. 0.]]
Observations on seasonal scaleΒΆ
Observation matrix for seasonal D47 and d18Oc data from individual D47 and d18Oc valuesΒΆ
InΒ [28]:
# Create the observation matrix H for seasonal data based on individual measurements
# Number of seasonally averaged measurements
H_seasonal = np.zeros((N_measurements, len(mu_prior_seasonal_combined)))
# Fill the first part of the observation matrix H with ones at the positions corresponding to the D47 measurements, ignoring NaN values
for i, measurement in enumerate([m for m in Caldarescu_data_dict if not pd.isna(m["D47"])]):
time_index = int(measurement["season_score"])
H_seasonal[i, time_index] = 1 # Indexing the first set of columns (1:4) for SST_D47
# Fill the second part of the observation matrix H with ones at the positions corresponding to the d18Oc measurements, ignoring NaN values
for i, measurement in enumerate([m for m in Caldarescu_data_dict if not pd.isna(m["Final_d18Ocarb_VPDB"])]):
time_index = int(measurement["season_score"])
H_seasonal[i + len(D47_measurements_individual), time_index + 8] = 1 # Indexing the third column block (8:12) for d18Oc
print(H_seasonal)
[[0. 0. 1. ... 0. 0. 0.] [0. 0. 1. ... 0. 0. 0.] [0. 0. 1. ... 0. 0. 0.] ... [0. 0. 0. ... 0. 0. 0.] [0. 0. 0. ... 0. 0. 0.] [0. 0. 0. ... 0. 0. 0.]]
Measurement matrix for seasonally aggregated D47 valuesΒΆ
InΒ [29]:
# Define the list of season names for integer indexing
season_names = ["winter", "spring", "summer", "autumn"]
# Initialize dictionaries to store aggregated measurements and uncertainties per season
D47_measurements_seasonal = {season: [] for season in season_names}
d18Oc_measurements_seasonal = {season: [] for season in season_names}
D47_measurement_uncertainties_seasonal = {season: [] for season in season_names}
d18Oc_measurement_uncertainties_seasonal = {season: [] for season in season_names}
print(D47_measurements_seasonal)
for measurement in Caldarescu_data_dict:
if not pd.isna(measurement["D47"]):
season_index = int(measurement["season_score"])
D47_measurements_seasonal[season_names[season_index]].append(measurement["D47"])
D47_measurement_uncertainties_seasonal[season_names[season_index]].append(measurement["D47_SD"] ** 2)
if not pd.isna(measurement["Final_d18Ocarb_VPDB"]):
season_index = int(measurement["season_score"])
d18Oc_measurements_seasonal[season_names[season_index]].append(measurement["Final_d18Ocarb_VPDB"])
d18Oc_measurement_uncertainties_seasonal[season_names[season_index]].append(measurement["d18O_SD"] ** 2)
# Calculate weighted mean and propagated uncertainty for each season
D47_aggregated_measurements_seasonal = []
d18Oc_aggregated_measurements_seasonal = []
D47_aggregated_uncertainties_seasonal = []
d18Oc_aggregated_uncertainties_seasonal = []
for season in season_names:
if D47_measurements_seasonal[season]:
weights = 1 / np.array(D47_measurement_uncertainties_seasonal[season])
weighted_mean = np.sum(np.array(D47_measurements_seasonal[season]) * weights) / np.sum(weights)
measurement_variance = np.var(D47_measurements_seasonal[season], ddof=1)
propagated_uncertainty = np.sqrt(1 / np.sum(weights) + measurement_variance)
propagated_standard_error = propagated_uncertainty / np.sqrt(len(D47_measurements_seasonal[season])) # Assemble D47 data as SE (optional)
D47_aggregated_measurements_seasonal.append(weighted_mean)
D47_aggregated_uncertainties_seasonal.append(propagated_uncertainty ** 2)
if d18Oc_measurements_seasonal[season]:
weights = 1 / np.array(d18Oc_measurement_uncertainties_seasonal[season])
weighted_mean = np.sum(np.array(d18Oc_measurements_seasonal[season]) * weights) / np.sum(weights)
measurement_variance = np.var(d18Oc_measurements_seasonal[season], ddof=1)
propagated_uncertainty = np.sqrt(1 / np.sum(weights) + measurement_variance)
d18Oc_aggregated_measurements_seasonal.append(weighted_mean)
d18Oc_aggregated_uncertainties_seasonal.append(propagated_uncertainty ** 2)
# OPTIONAL: Lower boundary d18Oc varaince at 0.01 (equivalent to 0.1 per mil measuremetn uncertainty)
d18Oc_aggregated_uncertainties_seasonal = [max(uncertainty, 0.01) for uncertainty in d18Oc_aggregated_uncertainties_seasonal]
# Create the measurement matrix Z
Z_seasonal_aggregated = np.array(D47_aggregated_measurements_seasonal + d18Oc_aggregated_measurements_seasonal).reshape(-1, 1)
# Create the measurement uncertainty matrix R (diagonal matrix)
R_seasonal_aggregated = np.diag(D47_aggregated_uncertainties_seasonal + d18Oc_aggregated_uncertainties_seasonal) # Diagonal matrix of measurement uncertainties
print("Z_seasonal_aggregated:", Z_seasonal_aggregated)
print("R_seasonal_aggregated:", R_seasonal_aggregated)
{'winter': [], 'spring': [], 'summer': [], 'autumn': []}
Z_seasonal_aggregated: [[ 0.6203526 ]
[ 0.60752459]
[ 0.58873077]
[ 0.60630233]
[-1.66855491]
[-2.19754098]
[-4.05653846]
[-2.77790698]]
R_seasonal_aggregated: [[2.25072127e-03 0.00000000e+00 0.00000000e+00 0.00000000e+00
0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00]
[0.00000000e+00 2.33469318e-03 0.00000000e+00 0.00000000e+00
0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00]
[0.00000000e+00 0.00000000e+00 7.07753232e-04 0.00000000e+00
0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00]
[0.00000000e+00 0.00000000e+00 0.00000000e+00 1.88021380e-03
0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00]
[0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
1.22199900e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00]
[0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
0.00000000e+00 1.46025638e+00 0.00000000e+00 0.00000000e+00]
[0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
0.00000000e+00 0.00000000e+00 1.43614517e-01 0.00000000e+00]
[0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
0.00000000e+00 0.00000000e+00 0.00000000e+00 7.42231892e-01]]
Observation matrix for seasonally aggregated D47 dataΒΆ
InΒ [30]:
# Create the observation matrix H for seasonally aggregated data
# Define the number of seasons or months
num_seasons = 4
# Number of seasonally aggregated measurements
N_measurements_seasonal_aggregated = len(Z_seasonal_aggregated)
H_seasonal_aggregated = np.zeros((N_measurements_seasonal_aggregated, len(mu_prior_seasonal_combined)))
# Fill the observation matrix H with ones at the positions corresponding to the measurements
for i, measurement in enumerate(Z_seasonal_aggregated):
if i < N_measurements_seasonal_aggregated // 2:
time_index = i % num_seasons # Ensure the index is within the range of seasons
H_seasonal_aggregated[i, time_index] = 1
else:
time_index = i % num_seasons
H_seasonal_aggregated[i, time_index + 8] = 1
print(H_seasonal_aggregated)
[[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0.] [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]]
Observations on monthly scaleΒΆ
Measurement matrix for monthly aggregated D47 valuesΒΆ
InΒ [31]:
month_names = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] # List full month names
# Initialize dictionaries to store aggregated measurements and uncertainties per month
D47_measurements_monthly = {month: [] for month in month_names}
d18Oc_measurements_monthly = {month: [] for month in month_names}
D47_measurement_uncertainties_monthly = {month: [] for month in month_names}
d18Oc_measurement_uncertainties_monthly = {month: [] for month in month_names}
# Aggregate measurements and uncertainties per month
for measurement in Caldarescu_data_dict:
if not pd.isna(measurement["D47"]):
month_index = int(measurement["month_score"])
D47_measurements_monthly[month_names[month_index]].append(measurement["D47"])
D47_measurement_uncertainties_monthly[month_names[month_index]].append(measurement["D47_SD"] ** 2)
if not pd.isna(measurement["Final_d18Ocarb_VPDB"]):
month_index = int(measurement["month_score"])
d18Oc_measurements_monthly[month_names[month_index]].append(measurement["Final_d18Ocarb_VPDB"])
d18Oc_measurement_uncertainties_monthly[month_names[month_index]].append(measurement["d18O_SD"] ** 2)
# Calculate weighted mean and propagated uncertainty for each month
D47_aggregated_measurements_monthly = []
d18Oc_aggregated_measurements_monthly = []
D47_aggregated_uncertainties_monthly = []
d18Oc_aggregated_uncertainties_monthly = []
for month in month_names:
if D47_measurements_monthly[month]:
weights = 1 / np.array(D47_measurement_uncertainties_monthly[month])
weighted_mean = np.sum(np.array(D47_measurements_monthly[month]) * weights) / np.sum(weights)
measurement_variance = np.var(D47_measurements_monthly[month], ddof=1)
propagated_uncertainty = np.sqrt(1 / np.sum(weights) + measurement_variance)
propagated_standard_error = propagated_uncertainty / np.sqrt(len(D47_measurements_monthly[month])) # Assemble D47 data as SE (optional)
D47_aggregated_measurements_monthly.append(weighted_mean)
D47_aggregated_uncertainties_monthly.append(propagated_uncertainty ** 2) # Assemble D47 data as SE
if d18Oc_measurements_monthly[month]:
weights = 1 / np.array(d18Oc_measurement_uncertainties_monthly[month])
weighted_mean = np.sum(np.array(d18Oc_measurements_monthly[month]) * weights) / np.sum(weights)
measurement_variance = np.var(d18Oc_measurements_monthly[month], ddof=1)
propagated_uncertainty = np.sqrt(1 / np.sum(weights) + measurement_variance)
d18Oc_aggregated_measurements_monthly.append(weighted_mean)
d18Oc_aggregated_uncertainties_monthly.append(propagated_uncertainty ** 2)
# OPTIONAL: Lower boundary d18Oc varaince at 0.01 (equivalent to 0.1 per mil measurement uncertainty)
d18Oc_aggregated_uncertainties_monthly = [max(uncertainty, 0.01) for uncertainty in d18Oc_aggregated_uncertainties_monthly]
# Create the measurement matrix Z
Z_monthly_aggregated = np.array(D47_aggregated_measurements_monthly + d18Oc_aggregated_measurements_monthly).reshape(-1, 1)
# Create the measurement uncertainty matrix R (diagonal matrix)
R_monthly_aggregated = np.diag(D47_aggregated_uncertainties_monthly + d18Oc_aggregated_uncertainties_monthly) # Diagonal matrix of measurement uncertainties
print("Z_monthly_aggregated:", Z_monthly_aggregated)
print("R_monthly_aggregated:", R_monthly_aggregated)
Z_monthly_aggregated: [[ 0.60602128] [ 0.62462903] [ 0.62673437] [ 0.60375 ] [ 0.61565217] [ 0.5994 ] [ 0.594375 ] [ 0.58444444] [ 0.588 ] [ 0.61542857] [ 0.616375 ] [ 0.60114286] [-2.8387234 ] [-1.22677419] [-1.2371875 ] [-1.7525 ] [-2.16 ] [-3.53 ] [-3.66625 ] [-4.02333333] [-4.43666667] [-1.96142857] [-1.995 ] [-3.20571429]] R_monthly_aggregated: [[2.09378060e-03 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [0.00000000e+00 2.79716801e-03 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [0.00000000e+00 0.00000000e+00 1.74234975e-03 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [0.00000000e+00 0.00000000e+00 0.00000000e+00 2.70733911e-03 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 2.41664911e-03 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 1.56013840e-03 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 8.77226574e-04 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 1.29751489e-03 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 4.15987114e-04 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 8.05531051e-04 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 1.12751229e-03 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 2.47134573e-03 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 3.77755613e-01 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 8.29783733e-01 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 8.54456323e-01 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 1.26294478e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 1.37155258e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 1.00000000e-02 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 1.53511819e-02 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 2.60399394e-02 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 9.39399394e-02 0.00000000e+00 0.00000000e+00 0.00000000e+00] [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 1.00000000e-02 0.00000000e+00 0.00000000e+00] [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 3.35243961e-02 0.00000000e+00] [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 6.01039623e-01]]
Observation matrix for monthly aggregated D47 dataΒΆ
InΒ [32]:
# Create the observation matrix H for monthly aggregated data
# Define the number of seasons or months
num_months = 12
# Number of monthly aggregated measurements
N_measurements_monthly_aggregated = len(Z_monthly_aggregated)
H_monthly_aggregated = np.zeros((N_measurements_monthly_aggregated, len(mu_prior_monthly_combined)))
# Fill the observation matrix H with ones at the positions corresponding to the measurements
for i, measurement in enumerate(Z_monthly_aggregated):
if i < N_measurements_monthly_aggregated // 2:
time_index = i % num_months # Ensure the index is within the range of months
H_monthly_aggregated[i, time_index] = 1
else:
time_index = i % num_months
H_monthly_aggregated[i, time_index + 24] = 1
print(H_monthly_aggregated)
[[1. 0. 0. ... 0. 0. 0.] [0. 1. 0. ... 0. 0. 0.] [0. 0. 1. ... 0. 0. 0.] ... [0. 0. 0. ... 0. 0. 0.] [0. 0. 0. ... 0. 0. 0.] [0. 0. 0. ... 0. 0. 0.]]
DEFINE UPDATING FUNCTIONSΒΆ
Create updating function (Kalman filter)ΒΆ
- Include updating of additional variables (SAT and precipitation) through cross-covariance with measured variables (D47_SST and d18Oc)
- Use block updating
Input:
- Prior means (mu_prior)
- Prior covariance matrix (P)
- Observation matrix (H)
- Measurement matrix (Z)
- Uncertainty matrix (R)
Output:
- Posterior means (mu_post)
- Posterior covariance matrix (P_post)
InΒ [33]:
def kalman_update_block(
mu_prior: np.ndarray,
cov_prior: np.ndarray,
Z: np.ndarray,
R: np.ndarray,
H: np.ndarray,
debug_print: bool = False
):
"""
Perform a Kalman update step for a block of observations.
Parameters:
mu_prior (np.ndarray): The prior mean vector.
cov_prior (np.ndarray): The prior covariance matrix.
Z (np.ndarray): The measurement matrix.
R (np.ndarray): The measurement noise covariance matrix.
H (np.ndarray): The observation matrix.
debug_print (bool): If True, print debug statements.
Returns:
mu_posterior (np.ndarray): The posterior mean vector.
cov_posterior (np.ndarray): The posterior covariance matrix.
"""
if debug_print:
# Print shapes of key variables for debugging
print("Shape of cov_prior:", cov_prior.shape)
print("Shape of H:", H.shape)
print("Shape of R:", R.shape)
print("Shape of mu_prior:", mu_prior.shape)
print("Shape of Z:", Z.shape)
# Compute the Kalman gain
K = cov_prior @ H.T @ np.linalg.inv(H @ cov_prior @ H.T + R)
if debug_print:
print("Shape of K:", K.shape)
# In-between steps for debugging
Y_hat = H @ mu_prior # Compute the predicted observation
if debug_print:
print("Shape of Y_hat:", Y_hat.shape)
innovation = Z - Y_hat.reshape(-1, 1) # Compute the innovation
if debug_print:
print("Shape of innovation:", innovation.shape)
kalman_gain = K @ innovation # Compute the Kalman gain
if debug_print:
print("Shape of kalman_gain:", kalman_gain.shape)
# Update the posterior mean estimate
mu_posterior = mu_prior + kalman_gain.flatten()
# Update the posterior covariance estimate
cov_posterior = cov_prior - K @ H @ cov_prior
return mu_posterior, cov_posterior
Create function to track the statistics of the likelihood (combining just the reconstruction data)ΒΆ
InΒ [34]:
# Create function to keep track of the likelihood statistics and data
# Suppress FutureWarning
# warnings.simplefilter(action = 'ignore', category = FutureWarning)
def likelihood_statistics_multi(
weighted_sum,
effective_weights_total,
n_update,
data_library,
measurement,
timestamp,
timestamp_sd,
Variable_names = ["Variable_name1", "Variable_name2"],
Variable_names_SDs = ["Variable_name_SD1", "Variable_name_SD2"]
):
"""
Incrementally updates the likelihood statistics for seasonal data.
Parameters:
- weighted_sum: list
List tracking the mean times the effective weight for each time bin and variable.
- effective_weights_total: list
List tracking the sum of effective weights for each time bin and variable.
- n_update: list
List tracking the number of datapoints for each time bin and variable.
- data_library: dict
Dictionary tracking individual data points and their uncertainties.
- measurement: dict
A single measurement containing data on multiple variables.
- timestamp: str
Key in the measurement dictionary for the timestamp (0-based index).
- timestamp_sd: float
Standard deviation of uncertainty in the timestamp.
- Variable_name: list of str
Key in the measurement dictionary for the variables (e.g. d18Oc, D47).
- Variable_name_SD: list of str
Key in the measurement dictionary for the standard deviation on the variables (e.g. d18Oc, D47).
"""
# Check if at least one combination of variable name and its SD is present in the measurement
found = False
for var_name, var_sd_name in zip(Variable_names, Variable_names_SDs):
if var_name in measurement and var_sd_name in measurement:
found = True
break
if timestamp in measurement and found:
# Extract the time and data values from the measurement
time = measurement[timestamp]
time_sd = measurement[timestamp_sd]
# Loop through all variable/SD pairs
for var_name, var_sd_name in zip(Variable_names, Variable_names_SDs):
if var_name in measurement and var_sd_name in measurement:
data_val = measurement[var_name]
data_sd = measurement[var_sd_name]
# Check if the data is valid
if not np.isnan(data_val) and not np.isnan(data_sd):
# Calculate the weight (inverse of variance)
weight = 1 / (data_sd ** 2)
# Determine the number of bins
num_bins_seasonal = int(len(weighted_sum) / len(Variable_names))
# Ensure num_bins_seasonal is an integer
bin_indices = np.arange(num_bins_seasonal, dtype=np.float64)
# Calculate the probability density for each bin
if time_sd == 0: # Catch cases where the time uncertainty is zero (or unknown)
probabilities = np.zeros(num_bins_seasonal, dtype=np.float64)
bin_index = int(time) % num_bins_seasonal # Ensure the bin index is within range
probabilities[bin_index] = 1 # Set the probability to 1 for the correct bin
else:
probabilities = stats.norm.pdf(bin_indices, loc=time, scale=time_sd) # For non-zero time uncertainty, use a normal distribution
probabilities /= probabilities.sum() # Normalize to ensure the sum of probabilities is 1
for i, prob in enumerate(probabilities): # Loop over all possible bin numbers in the probability vector
bin_index = i % num_bins_seasonal # Wrap around to the first bin if it overflows
# Update the weighted sums and sample count
effective_weight = weight * prob
var_idx = Variable_names.index(var_name) # Find the index of the variable
idx = int(var_idx * num_bins_seasonal + bin_index) # Unique index for (variable, bin)
if weighted_sum[idx] is None:
weighted_sum[idx] = 0
effective_weights_total[idx] = 0
weighted_sum[idx] = weighted_sum[idx] + data_val * effective_weight
effective_weights_total[idx] = effective_weights_total[idx] + effective_weight
# Update n_update for the correct variable and bin
var_idx = Variable_names.index(var_name) # Find the index of the variable
n_update[var_idx * num_bins_seasonal + (int(time) % num_bins_seasonal)] += 1 # update sample number per bin and variable
# Track individual data points and their uncertainties
key = (var_name, int(time)) # Store individual data points in a dictionary with (variable, time) as key
if key not in data_library:
data_library[key] = [] # Initialize the list for a new (time, var_name) pair
data_library[key].append((time_sd, data_val, data_sd))
return weighted_sum, effective_weights_total, n_update, data_library # Return the updated values
TEST UPDATING FUNCTIONS' SENSITIVITY TO INPUT UNCERTAINTYΒΆ
Load measurement data on SST, SAT, SSS and precipitationΒΆ
SST measurement dataΒΆ
InΒ [35]:
sst_measurement_df = pd.read_csv("Modern case/measurement data/isla_senoritasst3_5_summary.csv")
sst_measurement_df.head()
# Calculate mean and std for each month across all years (skip 'Year' column)
mu_measurement_SST_monthly = sst_measurement_df.loc[:, sst_measurement_df.columns != 'Year'].mean(axis=0, skipna=True).values
std_measurement_SST_monthly = sst_measurement_df.loc[:, sst_measurement_df.columns != 'Year'].std(axis=0, skipna=True).values
print(mu_measurement_SST_monthly)
print(std_measurement_SST_monthly)
[26.05397715 22.93767813 22.17705242 25.16571014 27.72303465 28.34105633 28.21574736 28.1912413 28.23161188 27.94042177 27.66587677 27.19749893] [0.85420343 1.0662528 2.00874825 1.49485497 0.61468902 0.33940817 0.30035706 0.29329485 0.28079002 0.33481732 0.46196816 0.73687447]
Convert SST measurement data to D47 for comparison in D47 domainΒΆ
InΒ [36]:
# Apply T47()-function from the D47calib package to all SST data
# Define a function to convert SST to D47 and also return the standard error
def sst_to_d47(sst):
if pd.isna(sst):
return pd.Series({'D47': np.nan, 'D47_SE': np.nan})
d47, d47_se = D47c.OGLS23.T47(T = sst)
return pd.Series({'D47': d47, 'D47_SE': d47_se})
# Apply the conversion to each month
month_abbr = ['ja', 'fb', 'mr', 'ar', 'my', 'jn', 'jl', 'ag', 'sp', 'ot', 'nv', 'dc']
# Create new columns for D47 and D47_SE
sst_measurement_D47_df = pd.DataFrame()
for month in month_abbr:
sst_measurement_D47_df[[f'{month}_D47', f'{month}_D47_SE']] = sst_measurement_df[month].apply(sst_to_d47)
# Display the updated DataFrame
sst_measurement_D47_df.head()
# Calculate mean and std for each month across all years (skip 'Year' column)
mu_measurement_SST_D47_monthly = sst_measurement_D47_df[[f'{month}_D47' for month in month_abbr]].mean(axis=0, skipna=True).values
std_measurement_SST_D47_monthly = (
sst_measurement_D47_df[[f'{month}_D47' for month in month_abbr]].std(axis=0, skipna=True).values ** 2 + # Standard deviation of the D47 values (squared to get variance)
sst_measurement_D47_df[[f'{month}_D47_SE' for month in month_abbr]].mean(axis=0, skipna=True).values ** 2 # Standard error as estimate for the uncertainty in the calibration (squared to get variance)
) ** 0.5 # Combine variances and take square root to get standard deviation
print(mu_measurement_SST_D47_monthly)
print(std_measurement_SST_D47_monthly)
[0.59024743 0.5996984 0.60209239 0.59292986 0.58530679 0.58349698 0.583862 0.58393343 0.58381556 0.58466662 0.58547229 0.58685417] [0.00276576 0.00345025 0.00632303 0.00463432 0.00207955 0.00143587 0.0013616 0.00134927 0.00132622 0.00143199 0.00171211 0.00242267]
SSS measurement dataΒΆ
Load SSS ESA satellite dataΒΆ
InΒ [37]:
# Load ESA SSS data as prior and show data structure
ESA_SSS = pd.read_csv('Modern case/SSS_Panama_all.csv') # Load the data into Python and in the Jupyter environment.
# Convert 'timeseries' column to datetime
ESA_SSS['date'] = pd.to_datetime(ESA_SSS['date'], format='%Y%m%d')
# Rename longitude and latitude columns to 'lon' and 'lat'
ESA_SSS = ESA_SSS.rename(columns={'longitude': 'lon', 'latitude': 'lat'})
# Extract month as two-letter code
month_abbr = ['ja', 'fb', 'mr', 'ar', 'my', 'jn', 'jl', 'ag', 'sp', 'ot', 'nv', 'dc']
ESA_SSS['month'] = ESA_SSS['date'].dt.month.apply(lambda x: month_abbr[x-1])
# Group by lat, lon, and month, then calculate the mean salinity for each group
ESA_SSS = ESA_SSS.groupby(['lat', 'lon', 'month'], as_index=False)['salinity'].mean()
# Pivot to make one column per month, keeping all data
ESA_SSS = ESA_SSS.pivot(index=['lat', 'lon'], columns='month', values='salinity').reset_index()
ESA_SSS.head()
Out[37]:
| month | lat | lon | ag | ar | dc | fb | ja | jl | jn | mr | my | nv | ot | sp |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 7.0 | -81.5 | 31.136181 | 33.602102 | 29.170102 | 32.022884 | 30.418702 | 31.581640 | 32.324151 | 33.077173 | 33.040741 | 29.530418 | 30.622286 | 30.665430 |
| 1 | 7.0 | -80.0 | 30.547831 | 33.757785 | 29.370818 | 33.184081 | 31.357592 | 30.910315 | 31.892236 | 33.637193 | 33.068963 | 29.336099 | 29.891578 | 29.889138 |
| 2 | 7.0 | -79.5 | 30.542582 | 33.867599 | 29.344715 | 33.139511 | 31.427747 | 31.004266 | 31.869194 | 33.656158 | 33.013782 | 29.361235 | 29.827726 | 30.019896 |
| 3 | 7.0 | -79.0 | 30.390912 | 33.637158 | 29.144642 | 32.730496 | 31.008538 | 31.002376 | 31.710107 | 33.398851 | 32.775925 | 29.262633 | 29.721688 | 29.765914 |
| 4 | 7.0 | -78.5 | 30.493174 | 33.578397 | 29.022538 | 32.151029 | 30.534165 | 31.075004 | 31.585485 | 33.202158 | 32.854144 | 29.120639 | 29.612904 | 29.680376 |
Extract monthly and seasonal measurement dataΒΆ
InΒ [38]:
# Create list of month names
months = ['ja', 'fb', 'mr', 'ar', 'my', 'jn', 'jl', 'ag', 'sp', 'ot', 'nv', 'dc']
mu_measurement_SSS_monthly = np.array(ESA_SSS[[f"{month}" for month in months]].mean(axis=0, skipna=True))
cov_measurement_SSS_monthly = np.cov(ESA_SSS[[f"{month}" for month in months]].dropna(), rowvar=False)
std_measurement_SSS_monthly = np.sqrt(np.diag(cov_measurement_SSS_monthly))
# Define the seasons
seasons = {
"winter": ["dc", "ja", "fb"],
"spring": ["mr", "ar", "my"],
"summer": ["jn", "jl", "ag"],
"autumn": ["sp", "ot", "nv"],
}
mu_measurement_SSS_seasonal = np.array([ESA_SSS[seasons[season]].mean(axis=1, skipna=True).mean() for season in seasons])
cov_measurement_SSS_seasonal = np.cov([ESA_SSS[seasons[season]].mean(axis=1, skipna=True) for season in seasons], rowvar=True)
std_measurement_SSS_seasonal = np.sqrt(np.diag(cov_measurement_SSS_seasonal))
Estimate seawater oxygen isotope value from SSS based on modern Gulf of Panama d18Ow-salinity relationship by Graniero et al. (2017; rainy season)ΒΆ
InΒ [39]:
# Apply the d18Ow-SSS function from Graniero et al. (2017) to SSS data
# Identify the SSS columns
mu_measurement_d18Ow_monthly = -7.89 + 0.23 * mu_measurement_SSS_monthly.copy()
std_measurement_d18Ow_monthly = std_measurement_SSS_monthly.copy() * 0.23 # Convert SSS uncertainty to d18Ow uncertainty
print(mu_measurement_d18Ow_monthly)
print(std_measurement_d18Ow_monthly)
[-0.10042807 0.10322898 0.18594921 0.20426716 0.11048993 -0.03567929 -0.11890993 -0.1688518 -0.24398497 -0.23124787 -0.30421288 -0.32621756] [0.49096871 0.32583766 0.2580445 0.23462769 0.3023568 0.41988153 0.48045419 0.5249147 0.58404172 0.62362581 0.67345612 0.6394892 ]
Calculate d18Oc values from d18Ow and SST values using Grossman and Ku (1986) with the VPDB-VSMOW scale correction by Gonfiantini et al. (1995) and Dettman et al. (1999)ΒΆ
InΒ [40]:
# Calculate d18Oc from SST and d18Ow using Grossman and Ku (1986) equation with updated d18Ow values
mu_measurement_d18Oc_monthly = (20.6 - mu_measurement_SST_monthly) / 4.34 + (mu_measurement_d18Ow_monthly - 0.27)
std_measurement_d18Oc_monthly = (
(1 / 4.34) ** 2 * std_measurement_SST_monthly ** 2 + # Variance from SST, corrected using the derivative of the d18Oc-temperature equation
std_measurement_d18Ow_monthly ** 2 # Variance from d18Ow
) ** 0.5 # Combine variances and take square root to get standard deviation
print(mu_measurement_d18Oc_monthly)
print(std_measurement_d18Oc_monthly)
[-1.62710484 -0.70540653 -0.44742692 -1.11773979 -1.8007623 -2.08933282 -2.14369043 -2.18798574 -2.27242089 -2.1925893 -2.20229508 -2.1163786 ] [0.52895067 0.40807968 0.5299176 0.41675742 0.3338857 0.4271024 0.48541302 0.52924704 0.58761431 0.62837949 0.68181635 0.6616449 ]
SAT measurement dataΒΆ
InΒ [41]:
sat_measurement_df = pd.read_csv("Modern case/measurement data/culebra_SAT_summary.csv")
sat_measurement_df.head()
# Calculate mean and std for each month across all years (skip 'Year' column)
mu_measurement_SAT_monthly = sat_measurement_df.loc[:, sat_measurement_df.columns != 'Year'].mean(axis=0, skipna=True).values
std_measurement_SAT_monthly = sat_measurement_df.loc[:, sat_measurement_df.columns != 'Year'].std(axis=0, skipna=True).values
print(mu_measurement_SAT_monthly)
print(std_measurement_SAT_monthly)
[27.21333333 27.09333333 27.16 27.4 27.65333333 27.44666667 27.30666667 27.24666667 27.22666667 27.03333333 26.64666667 27.09333333] [0.54754865 0.46053799 0.52345009 0.4840307 0.35429339 0.44859569 0.47429145 0.34819261 0.36735865 0.41518785 0.55660536 0.81544612]
Precipitation measurement dataΒΆ
InΒ [42]:
precip_measurement_df = pd.read_csv("Modern case/measurement data/culebra_precip_summary.csv")
# Convert from mm/month to mm/day by dividing by the number of days per month
days_per_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
for i, col in enumerate(month_abbr):
precip_measurement_df[col] = precip_measurement_df[col] / days_per_month[i]
precip_measurement_df.head()
# Calculate mean and std for each month across all years (skip 'Year' column)
mu_measurement_precip_monthly = precip_measurement_df.loc[:, precip_measurement_df.columns != 'Year'].mean(axis=0, skipna=True).values
std_measurement_precip_monthly = precip_measurement_df.loc[:, precip_measurement_df.columns != 'Year'].std(axis=0, skipna=True).values
print(mu_measurement_precip_monthly)
print(std_measurement_precip_monthly)
[0.83193548 0.24839286 0.67225806 1.649 5.48822581 6.66116667 5.12903226 5.50230415 6.34936508 6.50076805 7.35904762 3.10875576] [1.10390764 0.40655457 0.63890035 1.18891228 2.18837908 2.38718687 2.5753094 2.89484492 2.61863124 1.68515274 2.42597887 1.91788101]
Sensitivity testing for role of uncertainty and aggregation of dataΒΆ
Aggregate data while testing different combinations of enhanced uncertaintyΒΆ
InΒ [43]:
# Individual datapoints
mu_post_monthlySC, cov_post_monthlySC = kalman_update_block(
mu_prior_monthly_combined,
cov_prior_monthly_combined,
Z_individual,
R_individual,
H_monthly
)
# Extract posterior means and covariance for SST D47
mu_post_SST_D47_monthlySC = mu_post_monthlySC[:len(mu_prior_SST_D47_monthly)]
cov_post_SST_D47_monthlySC = cov_post_monthlySC[:len(mu_prior_SST_D47_monthly), :len(mu_prior_SST_D47_monthly)]
# Extract posterior means and covariance for SAT D47
mu_post_SAT_D47_monthlySC = mu_post_monthlySC[len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly)]
cov_post_SAT_D47_monthlySC = cov_post_monthlySC[len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly), len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly)]
# Extract posterior means and covariance for d18Oc
mu_post_d18Oc_monthlySC = mu_post_monthlySC[2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly)]
cov_post_d18Oc_monthlySC = cov_post_monthlySC[2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly), 2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly)]
# Aggregated data
mu_post_monthly_aggregated, cov_post_monthly_aggregated = kalman_update_block(
mu_prior_monthly_combined,
cov_prior_monthly_combined,
Z_monthly_aggregated,
R_monthly_aggregated,
H_monthly_aggregated
)
# Extract posterior means and covariance for SST D47
mu_post_SST_D47_monthly_aggregated = mu_post_monthly_aggregated[:len(mu_prior_SST_D47_monthly)]
cov_post_SST_D47_monthly_aggregated = cov_post_monthly_aggregated[:len(mu_prior_SST_D47_monthly), :len(mu_prior_SST_D47_monthly)]
# Extract posterior means and covariance for SAT D47
mu_post_SAT_D47_monthly_aggregated = mu_post_monthly_aggregated[len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly)]
cov_post_SAT_D47_monthly_aggregated = cov_post_monthly_aggregated[len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly), len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly)]
# Extract posterior means and covariance for d18Oc
mu_post_d18Oc_monthly_aggregated = mu_post_monthly_aggregated[2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly)]
cov_post_d18Oc_monthly_aggregated = cov_post_monthly_aggregated[2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly), 2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly)]
# --- HIGHER UNCERTAINTY SCENARIOS ---
# Individual data with higher uncertainty on both D47 and d18O measurements
R_individual_HU = R_individual * 10 # Increase uncertainty by a factor of 10
mu_post_monthlySC_HU, cov_post_monthlySC_HU = kalman_update_block(
mu_prior_monthly_combined,
cov_prior_monthly_combined,
Z_individual,
R_individual_HU,
H_monthly
)
# Extract posterior means and covariance for SST D47
mu_post_SST_D47_monthlySC_HU = mu_post_monthlySC_HU[:len(mu_prior_SST_D47_monthly)]
cov_post_SST_D47_monthlySC_HU = cov_post_monthlySC_HU[:len(mu_prior_SST_D47_monthly), :len(mu_prior_SST_D47_monthly)]
# Extract posterior means and covariance for SAT D47
mu_post_SAT_D47_monthlySC_HU = mu_post_monthlySC_HU[len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly)]
cov_post_SAT_D47_monthlySC_HU = cov_post_monthlySC_HU[len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly), len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly)]
# Extract posterior means and covariance for d18Oc
mu_post_d18Oc_monthlySC_HU = mu_post_monthlySC_HU[2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly)]
cov_post_d18Oc_monthlySC_HU = cov_post_monthlySC_HU[2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly), 2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly)]
# Aggregated data with higher uncertainty on both D47 and d18O measurements
R_monthly_aggregated_HU = R_monthly_aggregated * 10 # Increase uncertainty by a factor of 10
mu_post_monthly_aggregated_HU, cov_post_monthly_aggregated_HU = kalman_update_block(
mu_prior_monthly_combined,
cov_prior_monthly_combined,
Z_monthly_aggregated,
R_monthly_aggregated_HU,
H_monthly_aggregated
)
# Extract posterior means and covariance for SST D47
mu_post_SST_D47_monthly_aggregated_HU = mu_post_monthly_aggregated_HU[:len(mu_prior_SST_D47_monthly)]
cov_post_SST_D47_monthly_aggregated_HU = cov_post_monthly_aggregated_HU[:len(mu_prior_SST_D47_monthly), :len(mu_prior_SST_D47_monthly)]
# Extract posterior means and covariance for SAT D47
mu_post_SAT_D47_monthly_aggregated_HU = mu_post_monthly_aggregated_HU[len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly)]
cov_post_SAT_D47_monthly_aggregated_HU = cov_post_monthly_aggregated_HU[len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly), len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly)]
# Extract posterior means and covariance for d18Oc
mu_post_d18Oc_monthly_aggregated_HU = mu_post_monthly_aggregated_HU[2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly)]
cov_post_d18Oc_monthly_aggregated_HU = cov_post_monthly_aggregated_HU[2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly), 2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly)]
# Individual data with higher uncertainty only on D47 measurements
R_individual_HU_D47 = R_individual.copy()
R_individual_HU_D47[:len(D47_measurement_uncertainties_individual), :len(D47_measurement_uncertainties_individual)] *= 10 # Increase uncertainty of D47 measurements by a factor of 10
mu_post_monthlySC_HU_D47, cov_post_monthlySC_HU_D47 = kalman_update_block(
mu_prior_monthly_combined,
cov_prior_monthly_combined,
Z_individual,
R_individual_HU_D47,
H_monthly
)
# Extract posterior means and covariance for SST D47
mu_post_SST_D47_monthlySC_HU_D47 = mu_post_monthlySC_HU_D47[:len(mu_prior_SST_D47_monthly)]
cov_post_SST_D47_monthlySC_HU_D47 = cov_post_monthlySC_HU_D47[:len(mu_prior_SST_D47_monthly), :len(mu_prior_SST_D47_monthly)]
# Extract posterior means and covariance for SAT D47
mu_post_SAT_D47_monthlySC_HU_D47 = mu_post_monthlySC_HU_D47[len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly)]
cov_post_SAT_D47_monthlySC_HU_D47 = cov_post_monthlySC_HU_D47[len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly), len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly)]
# Extract posterior means and covariance for d18Oc
mu_post_d18Oc_monthlySC_HU_D47 = mu_post_monthlySC_HU_D47[2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly)]
cov_post_d18Oc_monthlySC_HU_D47 = cov_post_monthlySC_HU_D47[2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly), 2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly)]
# Aggregated data with higher uncertainty only on D47 measurements
R_monthly_aggregated_HU_D47 = R_monthly_aggregated.copy()
R_monthly_aggregated_HU_D47[:len(D47_aggregated_uncertainties_monthly), :len(D47_aggregated_uncertainties_monthly)] *= 10 # Increase uncertainty of D47 measurements by a factor of 10
mu_post_monthly_aggregated_HU_D47, cov_post_monthly_aggregated_HU_D47 = kalman_update_block(
mu_prior_monthly_combined,
cov_prior_monthly_combined,
Z_monthly_aggregated,
R_monthly_aggregated_HU_D47,
H_monthly_aggregated
)
# Extract posterior means and covariance for SST D47
mu_post_SST_D47_monthly_aggregated_HU_D47 = mu_post_monthly_aggregated_HU_D47[:len(mu_prior_SST_D47_monthly)]
cov_post_SST_D47_monthly_aggregated_HU_D47 = cov_post_monthly_aggregated_HU_D47[:len(mu_prior_SST_D47_monthly), :len(mu_prior_SST_D47_monthly)]
# Extract posterior means and covariance for SAT D47
mu_post_SAT_D47_monthly_aggregated_HU_D47 = mu_post_monthly_aggregated_HU_D47[len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly)]
cov_post_SAT_D47_monthly_aggregated_HU_D47 = cov_post_monthly_aggregated_HU_D47[len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly), len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly)]
# Extract posterior means and covariance for d18Oc
mu_post_d18Oc_monthly_aggregated_HU_D47 = mu_post_monthly_aggregated_HU_D47[2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly)]
cov_post_d18Oc_monthly_aggregated_HU_D47 = cov_post_monthly_aggregated_HU_D47[2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly), 2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly)]
# Individual data with higher uncertainty only on d18Oc measurements
R_individual_HU_d18O = R_individual.copy()
R_individual_HU_d18O[len(D47_measurements_individual):, len(D47_measurements_individual):] *= 10 # Increase uncertainty of D47 measurements by a factor of 10
mu_post_monthlySC_HU_d18O, cov_post_monthlySC_HU_d18O = kalman_update_block(
mu_prior_monthly_combined,
cov_prior_monthly_combined,
Z_individual,
R_individual_HU_d18O,
H_monthly
)
# Extract posterior means and covariance for SST D47
mu_post_SST_D47_monthlySC_HU_d18O = mu_post_monthlySC_HU_d18O[:len(mu_prior_SST_D47_monthly)]
cov_post_SST_D47_monthlySC_HU_d18O = cov_post_monthlySC_HU_d18O[:len(mu_prior_SST_D47_monthly), :len(mu_prior_SST_D47_monthly)]
# Extract posterior means and covariance for SAT D47
mu_post_SAT_D47_monthlySC_HU_d18O = mu_post_monthlySC_HU_d18O[len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly)]
cov_post_SAT_D47_monthlySC_HU_d18O = cov_post_monthlySC_HU_d18O[len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly), len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly)]
# Extract posterior means and covariance for d18Oc
mu_post_d18Oc_monthlySC_HU_d18O = mu_post_monthlySC_HU_d18O[2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly)]
cov_post_d18Oc_monthlySC_HU_d18O = cov_post_monthlySC_HU_d18O[2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly), 2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly)]
# Aggregated data with higher uncertainty only on d18Oc measurements
R_monthly_aggregated_HU_d18O = R_monthly_aggregated.copy()
R_monthly_aggregated_HU_d18O[len(D47_aggregated_uncertainties_monthly):, len(D47_aggregated_uncertainties_monthly):] *= 10 # Increase uncertainty of D47 measurements by a factor of 10
mu_post_monthly_aggregated_HU_d18O, cov_post_monthly_aggregated_HU_d18O = kalman_update_block(
mu_prior_monthly_combined,
cov_prior_monthly_combined,
Z_monthly_aggregated,
R_monthly_aggregated_HU_d18O,
H_monthly_aggregated
)
# Extract posterior means and covariance for SST D47
mu_post_SST_D47_monthly_aggregated_HU_d18O = mu_post_monthly_aggregated_HU_d18O[:len(mu_prior_SST_D47_monthly)]
cov_post_SST_D47_monthly_aggregated_HU_d18O = cov_post_monthly_aggregated_HU_d18O[:len(mu_prior_SST_D47_monthly), :len(mu_prior_SST_D47_monthly)]
# Extract posterior means and covariance for SAT D47
mu_post_SAT_D47_monthly_aggregated_HU_d18O = mu_post_monthly_aggregated_HU_d18O[len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly)]
cov_post_SAT_D47_monthly_aggregated_HU_d18O = cov_post_monthly_aggregated_HU_d18O[len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly), len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly)]
# Extract posterior means and covariance for d18Oc
mu_post_d18Oc_monthly_aggregated_HU_d18O = mu_post_monthly_aggregated_HU_d18O[2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly)]
cov_post_d18Oc_monthly_aggregated_HU_d18O = cov_post_monthly_aggregated_HU_d18O[2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly), 2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly)]
# --- LOWER UNCERTAINTY SCENARIOS ---
# Individual data with lower uncertainty on both D47 and d18O measurements
R_individual_LU = R_individual / 10 # Decrease uncertainty by a factor of 10
mu_post_monthlySC_LU, cov_post_monthlySC_LU = kalman_update_block(
mu_prior_monthly_combined,
cov_prior_monthly_combined,
Z_individual,
R_individual_LU,
H_monthly
)
# Extract posterior means and covariance for SST D47
mu_post_SST_D47_monthlySC_LU = mu_post_monthlySC_LU[:len(mu_prior_SST_D47_monthly)]
cov_post_SST_D47_monthlySC_LU = cov_post_monthlySC_LU[:len(mu_prior_SST_D47_monthly), :len(mu_prior_SST_D47_monthly)]
# Extract posterior means and covariance for SAT D47
mu_post_SAT_D47_monthlySC_LU = mu_post_monthlySC_LU[len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly)]
cov_post_SAT_D47_monthlySC_LU = cov_post_monthlySC_LU[len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly), len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly)]
# Extract posterior means and covariance for d18Oc
mu_post_d18Oc_monthlySC_LU = mu_post_monthlySC_LU[2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly)]
cov_post_d18Oc_monthlySC_LU = cov_post_monthlySC_LU[2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly), 2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly)]
# Aggregated data with lower uncertainty on both D47 and d18O measurements
R_monthly_aggregated_LU = R_monthly_aggregated / 10 # Decrease uncertainty by a factor of 10
mu_post_monthly_aggregated_LU, cov_post_monthly_aggregated_LU = kalman_update_block(
mu_prior_monthly_combined,
cov_prior_monthly_combined,
Z_monthly_aggregated,
R_monthly_aggregated_LU,
H_monthly_aggregated
)
# Extract posterior means and covariance for SST D47
mu_post_SST_D47_monthly_aggregated_LU = mu_post_monthly_aggregated_LU[:len(mu_prior_SST_D47_monthly)]
cov_post_SST_D47_monthly_aggregated_LU = cov_post_monthly_aggregated_LU[:len(mu_prior_SST_D47_monthly), :len(mu_prior_SST_D47_monthly)]
# Extract posterior means and covariance for SAT D47
mu_post_SAT_D47_monthly_aggregated_LU = mu_post_monthly_aggregated_LU[len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly)]
cov_post_SAT_D47_monthly_aggregated_LU = cov_post_monthly_aggregated_LU[len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly), len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly)]
# Extract posterior means and covariance for d18Oc
mu_post_d18Oc_monthly_aggregated_LU = mu_post_monthly_aggregated_LU[2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly)]
cov_post_d18Oc_monthly_aggregated_LU = cov_post_monthly_aggregated_LU[2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly), 2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly)]
# Individual data with lower uncertainty only on D47 measurements
R_individual_LU_D47 = R_individual.copy()
R_individual_LU_D47[:len(D47_measurement_uncertainties_individual), :len(D47_measurement_uncertainties_individual)] /= 10 # Decrease uncertainty of D47 measurements by a factor of 10
mu_post_monthlySC_LU_D47, cov_post_monthlySC_LU_D47 = kalman_update_block(
mu_prior_monthly_combined,
cov_prior_monthly_combined,
Z_individual,
R_individual_LU_D47,
H_monthly
)
# Extract posterior means and covariance for SST D47
mu_post_SST_D47_monthlySC_LU_D47 = mu_post_monthlySC_LU_D47[:len(mu_prior_SST_D47_monthly)]
cov_post_SST_D47_monthlySC_LU_D47 = cov_post_monthlySC_LU_D47[:len(mu_prior_SST_D47_monthly), :len(mu_prior_SST_D47_monthly)]
# Extract posterior means and covariance for SAT D47
mu_post_SAT_D47_monthlySC_LU_D47 = mu_post_monthlySC_LU_D47[len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly)]
cov_post_SAT_D47_monthlySC_LU_D47 = cov_post_monthlySC_LU_D47[len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly), len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly)]
# Extract posterior means and covariance for d18Oc
mu_post_d18Oc_monthlySC_LU_D47 = mu_post_monthlySC_LU_D47[2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly)]
cov_post_d18Oc_monthlySC_LU_D47 = cov_post_monthlySC_LU_D47[2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly), 2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly)]
# Aggregated data with lower uncertainty only on D47 measurements
R_monthly_aggregated_LU_D47 = R_monthly_aggregated.copy()
R_monthly_aggregated_LU_D47[:len(D47_aggregated_uncertainties_monthly), :len(D47_aggregated_uncertainties_monthly)] /= 10 # Decrease uncertainty of D47 measurements by a factor of 10
mu_post_monthly_aggregated_LU_D47, cov_post_monthly_aggregated_LU_D47 = kalman_update_block(
mu_prior_monthly_combined,
cov_prior_monthly_combined,
Z_monthly_aggregated,
R_monthly_aggregated_LU_D47,
H_monthly_aggregated
)
# Extract posterior means and covariance for SST D47
mu_post_SST_D47_monthly_aggregated_LU_D47 = mu_post_monthly_aggregated_LU_D47[:len(mu_prior_SST_D47_monthly)]
cov_post_SST_D47_monthly_aggregated_LU_D47 = cov_post_monthly_aggregated_LU_D47[:len(mu_prior_SST_D47_monthly), :len(mu_prior_SST_D47_monthly)]
# Extract posterior means and covariance for SAT D47
mu_post_SAT_D47_monthly_aggregated_LU_D47 = mu_post_monthly_aggregated_LU_D47[len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly)]
cov_post_SAT_D47_monthly_aggregated_LU_D47 = cov_post_monthly_aggregated_LU_D47[len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly), len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly)]
# Extract posterior means and covariance for d18Oc
mu_post_d18Oc_monthly_aggregated_LU_D47 = mu_post_monthly_aggregated_LU_D47[2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly)]
cov_post_d18Oc_monthly_aggregated_LU_D47 = cov_post_monthly_aggregated_LU_D47[2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly), 2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly)]
# Individual data with lower uncertainty only on d18Oc measurements
R_individual_LU_d18O = R_individual.copy()
R_individual_LU_d18O[len(D47_measurements_individual):, len(D47_measurements_individual):] /= 10 # Decrease uncertainty of D47 measurements by a factor of 10
mu_post_monthlySC_LU_d18O, cov_post_monthlySC_LU_d18O = kalman_update_block(
mu_prior_monthly_combined,
cov_prior_monthly_combined,
Z_individual,
R_individual_LU_d18O,
H_monthly
)
# Extract posterior means and covariance for SST D47
mu_post_SST_D47_monthlySC_LU_d18O = mu_post_monthlySC_LU_d18O[:len(mu_prior_SST_D47_monthly)]
cov_post_SST_D47_monthlySC_LU_d18O = cov_post_monthlySC_LU_d18O[:len(mu_prior_SST_D47_monthly), :len(mu_prior_SST_D47_monthly)]
# Extract posterior means and covariance for SAT D47
mu_post_SAT_D47_monthlySC_LU_d18O = mu_post_monthlySC_LU_d18O[len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly)]
cov_post_SAT_D47_monthlySC_LU_d18O = cov_post_monthlySC_LU_d18O[len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly), len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly)]
# Extract posterior means and covariance for d18Oc
mu_post_d18Oc_monthlySC_LU_d18O = mu_post_monthlySC_LU_d18O[2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly)]
cov_post_d18Oc_monthlySC_LU_d18O = cov_post_monthlySC_LU_d18O[2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly), 2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly)]
# Aggregated data with lower uncertainty only on d18Oc measurements
R_monthly_aggregated_LU_d18O = R_monthly_aggregated.copy()
R_monthly_aggregated_LU_d18O[len(D47_aggregated_uncertainties_monthly):, len(D47_aggregated_uncertainties_monthly):] /= 10 # Decrease uncertainty of D47 measurements by a factor of 10
mu_post_monthly_aggregated_LU_d18O, cov_post_monthly_aggregated_LU_d18O = kalman_update_block(
mu_prior_monthly_combined,
cov_prior_monthly_combined,
Z_monthly_aggregated,
R_monthly_aggregated_LU_d18O,
H_monthly_aggregated
)
# Extract posterior means and covariance for SST D47
mu_post_SST_D47_monthly_aggregated_LU_d18O = mu_post_monthly_aggregated_LU_d18O[:len(mu_prior_SST_D47_monthly)]
cov_post_SST_D47_monthly_aggregated_LU_d18O = cov_post_monthly_aggregated_LU_d18O[:len(mu_prior_SST_D47_monthly), :len(mu_prior_SST_D47_monthly)]
# Extract posterior means and covariance for SAT D47
mu_post_SAT_D47_monthly_aggregated_LU_d18O = mu_post_monthly_aggregated_LU_d18O[len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly)]
cov_post_SAT_D47_monthly_aggregated_LU_d18O = cov_post_monthly_aggregated_LU_d18O[len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly), len(mu_prior_SST_D47_monthly):2*len(mu_prior_SST_D47_monthly)]
# Extract posterior means and covariance for d18Oc
mu_post_d18Oc_monthly_aggregated_LU_d18O = mu_post_monthly_aggregated_LU_d18O[2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly)]
cov_post_d18Oc_monthly_aggregated_LU_d18O = cov_post_monthly_aggregated_LU_d18O[2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly), 2*len(mu_prior_d18Oc_monthly):3*len(mu_prior_d18Oc_monthly)]
Print D47 posteriors and uncertainties in a tableΒΆ
InΒ [44]:
# Prepare the data for the table
data = {
"D47 data from:": [
"In situ measurements",
"Individual Measurements",
"Individual Measurements (SD D47 * 10)",
"Individual Measurements (SD d18Oc * 10)",
"Individual Measurements (both SDs * 10)",
"Individual Measurements (SD D47 * 0.1)",
"Individual Measurements (SD d18Oc * 0.1)",
"Individual Measurements (both SDs * 0.1)",
"Aggregated Measurements",
"Aggregated Measurements (SD D47 * 10)",
"Aggregated Measurements (SD d18Oc * 10)",
"Aggregated Measurements (both SDs * 10)",
"Aggregated Measurements (SD D47 * 0.1)",
"Aggregated Measurements (SD d18Oc * 0.1)",
"Aggregated Measurements (both SDs * 0.1)"
]
}
# Add monthly means and standard deviations to the data dictionary
months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
# Process and rename the means and standard deviations for each data source
D47_means_individual = mu_post_SST_D47_monthlySC
D47_stds_individual = np.sqrt(np.diag(cov_post_SST_D47_monthlySC))
D47_means_individual_hu_d47 = mu_post_SST_D47_monthlySC_HU_D47
D47_stds_individual_hu_d47 = np.sqrt(np.diag(cov_post_SST_D47_monthlySC_HU_D47))
D47_means_individual_hu_d18o = mu_post_SST_D47_monthlySC_HU_d18O
D47_stds_individual_hu_d18o = np.sqrt(np.diag(cov_post_SST_D47_monthlySC_HU_d18O))
D47_means_individual_hu = mu_post_SST_D47_monthlySC_HU
D47_stds_individual_hu = np.sqrt(np.diag(cov_post_SST_D47_monthlySC_HU))
D47_means_individual_lu_d47 = mu_post_SST_D47_monthlySC_LU_D47
D47_stds_individual_lu_d47 = np.sqrt(np.diag(cov_post_SST_D47_monthlySC_LU_D47))
D47_means_individual_lu_d18o = mu_post_SST_D47_monthlySC_LU_d18O
D47_stds_individual_lu_d18o = np.sqrt(np.diag(cov_post_SST_D47_monthlySC_LU_d18O))
D47_means_individual_lu = mu_post_SST_D47_monthlySC_LU
D47_stds_individual_lu = np.sqrt(np.diag(cov_post_SST_D47_monthlySC_LU))
D47_means_aggregated = mu_post_SST_D47_monthly_aggregated
D47_stds_aggregated = np.sqrt(np.diag(cov_post_SST_D47_monthly_aggregated))
D47_means_aggregated_hu_d47 = mu_post_SST_D47_monthly_aggregated_HU_D47
D47_stds_aggregated_hu_d47 = np.sqrt(np.diag(cov_post_SST_D47_monthly_aggregated_HU_D47))
D47_means_aggregated_hu_d18o = mu_post_SST_D47_monthly_aggregated_HU_d18O
D47_stds_aggregated_hu_d18o = np.sqrt(np.diag(cov_post_SST_D47_monthly_aggregated_HU_d18O))
D47_means_aggregated_hu = mu_post_SST_D47_monthly_aggregated_HU
D47_stds_aggregated_hu = np.sqrt(np.diag(cov_post_SST_D47_monthly_aggregated_HU))
D47_means_aggregated_lu_d47 = mu_post_SST_D47_monthly_aggregated_LU_D47
D47_stds_aggregated_lu_d47 = np.sqrt(np.diag(cov_post_SST_D47_monthly_aggregated_LU_D47))
D47_means_aggregated_lu_d18o = mu_post_SST_D47_monthly_aggregated_LU_d18O
D47_stds_aggregated_lu_d18o = np.sqrt(np.diag(cov_post_SST_D47_monthly_aggregated_LU_d18O))
D47_means_aggregated_lu = mu_post_SST_D47_monthly_aggregated_LU
D47_stds_aggregated_lu = np.sqrt(np.diag(cov_post_SST_D47_monthly_aggregated_LU))
# Combine all means and standard deviations into the data structure
all_D47_means = [
mu_measurement_SST_D47_monthly,
D47_means_individual,
D47_means_individual_hu_d47,
D47_means_individual_hu_d18o,
D47_means_individual_hu,
D47_means_individual_lu_d47,
D47_means_individual_lu_d18o,
D47_means_individual_lu,
D47_means_aggregated,
D47_means_aggregated_hu_d47,
D47_means_aggregated_hu_d18o,
D47_means_aggregated_hu,
D47_means_aggregated_lu_d47,
D47_means_aggregated_lu_d18o,
D47_means_aggregated_lu
]
all_D47_stds = [
std_measurement_SST_D47_monthly,
D47_stds_individual,
D47_stds_individual_hu_d47,
D47_stds_individual_hu_d18o,
D47_stds_individual_hu,
D47_stds_individual_lu_d47,
D47_stds_individual_lu_d18o,
D47_stds_individual_lu,
D47_stds_aggregated,
D47_stds_aggregated_hu_d47,
D47_stds_aggregated_hu_d18o,
D47_stds_aggregated_hu,
D47_stds_aggregated_lu_d47,
D47_stds_aggregated_lu_d18o,
D47_stds_aggregated_lu
]
for i, month in enumerate(months):
data[f"{month} Mean"] = [means[i] for means in all_D47_means]
data[f"{month} Std"] = [stds[i] for stds in all_D47_stds]
# Create the Pandas DataFrame
D47_uncertainty_test_table_df = pd.DataFrame(data)
# Set the 'Data Type' column as the index
D47_uncertainty_test_table_df = D47_uncertainty_test_table_df.set_index("D47 data from:")
# Function to format mean and std
def format_mean_std(mean, std):
return f"{mean:.4f} Β± {std:.4f}"
# Apply the formatting function to the mean and std columns
for month in months:
D47_uncertainty_test_table_df[month] = D47_uncertainty_test_table_df.apply(lambda row: format_mean_std(row[f"{month} Mean"], row[f"{month} Std"]), axis=1)
D47_uncertainty_test_table_df = D47_uncertainty_test_table_df.drop(columns=[f"{month} Mean", f"{month} Std"])
# Print the table
print(D47_uncertainty_test_table_df.to_string())
# Export the table to a CSV file
D47_uncertainty_test_table_df.to_csv("Sensitivity_test_D47_d18Oc_uncertainties/D47_uncertainty_test_table.csv")
# Extract monthly means of measurements
mu_likelihood_monthly_D47 = Z_monthly_aggregated[:len(mu_prior_SST_D47_monthly)].flatten()
std_likelihood_monthly_D47 = np.sqrt(np.diag(R_monthly_aggregated))[:len(mu_prior_SST_D47_monthly)].flatten()
January February March April May June July August September October November December D47 data from: In situ measurements 0.5902 Β± 0.0028 0.5997 Β± 0.0035 0.6021 Β± 0.0063 0.5929 Β± 0.0046 0.5853 Β± 0.0021 0.5835 Β± 0.0014 0.5839 Β± 0.0014 0.5839 Β± 0.0013 0.5838 Β± 0.0013 0.5847 Β± 0.0014 0.5855 Β± 0.0017 0.5869 Β± 0.0024 Individual Measurements 0.5905 Β± 0.0006 0.6069 Β± 0.0005 0.6152 Β± 0.0005 0.6103 Β± 0.0005 0.5901 Β± 0.0006 0.5749 Β± 0.0006 0.5745 Β± 0.0006 0.5731 Β± 0.0006 0.5651 Β± 0.0006 0.5590 Β± 0.0006 0.5641 Β± 0.0007 0.5742 Β± 0.0006 Individual Measurements (SD D47 * 10) 0.5895 Β± 0.0006 0.6061 Β± 0.0005 0.6144 Β± 0.0005 0.6095 Β± 0.0005 0.5891 Β± 0.0006 0.5738 Β± 0.0006 0.5733 Β± 0.0007 0.5720 Β± 0.0007 0.5640 Β± 0.0007 0.5580 Β± 0.0006 0.5630 Β± 0.0007 0.5730 Β± 0.0007 Individual Measurements (SD d18Oc * 10) 0.5921 Β± 0.0009 0.6000 Β± 0.0008 0.6035 Β± 0.0008 0.6000 Β± 0.0008 0.5902 Β± 0.0009 0.5838 Β± 0.0010 0.5835 Β± 0.0011 0.5825 Β± 0.0011 0.5787 Β± 0.0011 0.5761 Β± 0.0010 0.5788 Β± 0.0011 0.5841 Β± 0.0011 Individual Measurements (both SDs * 10) 0.5891 Β± 0.0010 0.5973 Β± 0.0009 0.6009 Β± 0.0009 0.5974 Β± 0.0009 0.5872 Β± 0.0010 0.5807 Β± 0.0011 0.5802 Β± 0.0011 0.5793 Β± 0.0011 0.5757 Β± 0.0011 0.5733 Β± 0.0011 0.5757 Β± 0.0012 0.5808 Β± 0.0011 Individual Measurements (SD D47 * 0.1) 0.5968 Β± 0.0005 0.6123 Β± 0.0004 0.6199 Β± 0.0004 0.6153 Β± 0.0004 0.5964 Β± 0.0005 0.5819 Β± 0.0005 0.5817 Β± 0.0005 0.5804 Β± 0.0005 0.5721 Β± 0.0005 0.5656 Β± 0.0006 0.5714 Β± 0.0006 0.5814 Β± 0.0005 Individual Measurements (SD d18Oc * 0.1) 0.5727 Β± 0.0003 0.6001 Β± 0.0003 0.6149 Β± 0.0003 0.6086 Β± 0.0003 0.5753 Β± 0.0003 0.5480 Β± 0.0003 0.5462 Β± 0.0004 0.5445 Β± 0.0004 0.5333 Β± 0.0003 0.5247 Β± 0.0003 0.5308 Β± 0.0004 0.5452 Β± 0.0003 Individual Measurements (both SDs * 0.1) 0.5770 Β± 0.0003 0.6038 Β± 0.0003 0.6180 Β± 0.0002 0.6119 Β± 0.0003 0.5796 Β± 0.0003 0.5529 Β± 0.0003 0.5513 Β± 0.0003 0.5497 Β± 0.0003 0.5382 Β± 0.0003 0.5294 Β± 0.0003 0.5359 Β± 0.0003 0.5502 Β± 0.0003 Aggregated Measurements 0.5856 Β± 0.0012 0.5875 Β± 0.0012 0.5880 Β± 0.0012 0.5872 Β± 0.0012 0.5853 Β± 0.0011 0.5839 Β± 0.0013 0.5832 Β± 0.0014 0.5828 Β± 0.0014 0.5820 Β± 0.0014 0.5818 Β± 0.0014 0.5821 Β± 0.0014 0.5832 Β± 0.0014 Aggregated Measurements (SD D47 * 10) 0.5854 Β± 0.0012 0.5873 Β± 0.0012 0.5879 Β± 0.0012 0.5870 Β± 0.0012 0.5851 Β± 0.0011 0.5836 Β± 0.0013 0.5830 Β± 0.0014 0.5825 Β± 0.0014 0.5817 Β± 0.0014 0.5815 Β± 0.0014 0.5818 Β± 0.0015 0.5830 Β± 0.0014 Aggregated Measurements (SD d18Oc * 10) 0.5859 Β± 0.0013 0.5878 Β± 0.0012 0.5881 Β± 0.0013 0.5871 Β± 0.0012 0.5853 Β± 0.0012 0.5839 Β± 0.0014 0.5833 Β± 0.0015 0.5828 Β± 0.0015 0.5821 Β± 0.0015 0.5819 Β± 0.0015 0.5823 Β± 0.0016 0.5835 Β± 0.0015 Aggregated Measurements (both SDs * 10) 0.5857 Β± 0.0013 0.5876 Β± 0.0012 0.5880 Β± 0.0013 0.5869 Β± 0.0012 0.5850 Β± 0.0012 0.5837 Β± 0.0014 0.5830 Β± 0.0015 0.5825 Β± 0.0015 0.5818 Β± 0.0015 0.5817 Β± 0.0015 0.5820 Β± 0.0016 0.5832 Β± 0.0015 Aggregated Measurements (SD D47 * 0.1) 0.5876 Β± 0.0011 0.5891 Β± 0.0011 0.5893 Β± 0.0012 0.5885 Β± 0.0012 0.5871 Β± 0.0011 0.5861 Β± 0.0012 0.5856 Β± 0.0013 0.5852 Β± 0.0013 0.5842 Β± 0.0013 0.5839 Β± 0.0013 0.5844 Β± 0.0014 0.5856 Β± 0.0013 Aggregated Measurements (SD d18Oc * 0.1) 0.5864 Β± 0.0010 0.5896 Β± 0.0010 0.5909 Β± 0.0011 0.5901 Β± 0.0010 0.5870 Β± 0.0010 0.5835 Β± 0.0010 0.5827 Β± 0.0010 0.5824 Β± 0.0011 0.5808 Β± 0.0010 0.5800 Β± 0.0010 0.5808 Β± 0.0011 0.5826 Β± 0.0010 Aggregated Measurements (both SDs * 0.1) 0.5877 Β± 0.0009 0.5907 Β± 0.0010 0.5919 Β± 0.0010 0.5912 Β± 0.0010 0.5882 Β± 0.0009 0.5849 Β± 0.0009 0.5842 Β± 0.0010 0.5838 Β± 0.0010 0.5822 Β± 0.0010 0.5813 Β± 0.0010 0.5822 Β± 0.0010 0.5840 Β± 0.0010
Print d18Oc posteriors and uncertainties in a tableΒΆ
InΒ [45]:
# Prepare the data for the table
data = {
"d18Oc data from:": [
"In situ measurements",
"Individual Measurements",
"Individual Measurements (SD D47 * 10)",
"Individual Measurements (SD d18Oc * 10)",
"Individual Measurements (both SDs * 10)",
"Individual Measurements (SD D47 * 0.1)",
"Individual Measurements (SD d18Oc * 0.1)",
"Individual Measurements (both SDs * 0.1)",
"Aggregated Measurements",
"Aggregated Measurements (SD D47 * 10)",
"Aggregated Measurements (SD d18Oc * 10)",
"Aggregated Measurements (both SDs * 10)",
"Aggregated Measurements (SD D47 * 0.1)",
"Aggregated Measurements (SD d18Oc * 0.1)",
"Aggregated Measurements (both SDs * 0.1)"
]
}
# Add monthly means and standard deviations to the data dictionary
months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
# Process and rename the means and standard deviations for each data source
d18Oc_means_individual = mu_post_d18Oc_monthlySC
d18Oc_stds_individual = np.sqrt(np.diag(cov_post_d18Oc_monthlySC))
d18Oc_means_individual_hu_d47 = mu_post_d18Oc_monthlySC_HU_D47
d18Oc_stds_individual_hu_d47 = np.sqrt(np.diag(cov_post_d18Oc_monthlySC_HU_D47))
d18Oc_means_individual_hu_d18o = mu_post_d18Oc_monthlySC_HU_d18O
d18Oc_stds_individual_hu_d18o = np.sqrt(np.diag(cov_post_d18Oc_monthlySC_HU_d18O))
d18Oc_means_individual_hu = mu_post_d18Oc_monthlySC_HU
d18Oc_stds_individual_hu = np.sqrt(np.diag(cov_post_d18Oc_monthlySC_HU))
d18Oc_means_individual_lu_d47 = mu_post_d18Oc_monthlySC_LU_D47
d18Oc_stds_individual_lu_d47 = np.sqrt(np.diag(cov_post_d18Oc_monthlySC_LU_D47))
d18Oc_means_individual_lu_d18o = mu_post_d18Oc_monthlySC_LU_d18O
d18Oc_stds_individual_lu_d18o = np.sqrt(np.diag(cov_post_d18Oc_monthlySC_LU_d18O))
d18Oc_means_individual_lu = mu_post_d18Oc_monthlySC_LU
d18Oc_stds_individual_lu = np.sqrt(np.diag(cov_post_d18Oc_monthlySC_LU))
d18Oc_means_aggregated = mu_post_d18Oc_monthly_aggregated
d18Oc_stds_aggregated = np.sqrt(np.diag(cov_post_d18Oc_monthly_aggregated))
d18Oc_means_aggregated_hu_d47 = mu_post_d18Oc_monthly_aggregated_HU_D47
d18Oc_stds_aggregated_hu_d47 = np.sqrt(np.diag(cov_post_d18Oc_monthly_aggregated_HU_D47))
d18Oc_means_aggregated_hu_d18o = mu_post_d18Oc_monthly_aggregated_HU_d18O
d18Oc_stds_aggregated_hu_d18o = np.sqrt(np.diag(cov_post_d18Oc_monthly_aggregated_HU_d18O))
d18Oc_means_aggregated_hu = mu_post_d18Oc_monthly_aggregated_HU
d18Oc_stds_aggregated_hu = np.sqrt(np.diag(cov_post_d18Oc_monthly_aggregated_HU))
d18Oc_means_aggregated_lu_d47 = mu_post_d18Oc_monthly_aggregated_LU_D47
d18Oc_stds_aggregated_lu_d47 = np.sqrt(np.diag(cov_post_d18Oc_monthly_aggregated_LU_D47))
d18Oc_means_aggregated_lu_d18o = mu_post_d18Oc_monthly_aggregated_LU_d18O
d18Oc_stds_aggregated_lu_d18o = np.sqrt(np.diag(cov_post_d18Oc_monthly_aggregated_LU_d18O))
d18Oc_means_aggregated_lu = mu_post_d18Oc_monthly_aggregated_LU
d18Oc_stds_aggregated_lu = np.sqrt(np.diag(cov_post_d18Oc_monthly_aggregated_LU))
# Combine all means and standard deviations into the data structure
all_d18Oc_means = [
mu_measurement_d18Oc_monthly,
d18Oc_means_individual,
d18Oc_means_individual_hu_d47,
d18Oc_means_individual_hu_d18o,
d18Oc_means_individual_hu,
d18Oc_means_individual_lu_d47,
d18Oc_means_individual_lu_d18o,
d18Oc_means_individual_lu,
d18Oc_means_aggregated,
d18Oc_means_aggregated_hu_d47,
d18Oc_means_aggregated_hu_d18o,
d18Oc_means_aggregated_hu,
d18Oc_means_aggregated_lu_d47,
d18Oc_means_aggregated_lu_d18o,
d18Oc_means_aggregated_lu
]
all_d18Oc_stds = [
std_measurement_d18Oc_monthly,
d18Oc_stds_individual,
d18Oc_stds_individual_hu_d47,
d18Oc_stds_individual_hu_d18o,
d18Oc_stds_individual_hu,
d18Oc_stds_individual_lu_d47,
d18Oc_stds_individual_lu_d18o,
d18Oc_stds_individual_lu,
d18Oc_stds_aggregated,
d18Oc_stds_aggregated_hu_d47,
d18Oc_stds_aggregated_hu_d18o,
d18Oc_stds_aggregated_hu,
d18Oc_stds_aggregated_lu_d47,
d18Oc_stds_aggregated_lu_d18o,
d18Oc_stds_aggregated_lu
]
for i, month in enumerate(months):
data[f"{month} Mean"] = [means[i] for means in all_d18Oc_means]
data[f"{month} Std"] = [stds[i] for stds in all_d18Oc_stds]
# Create the Pandas DataFrame
d18Oc_uncertainty_test_table_df = pd.DataFrame(data)
# Set the 'Data Type' column as the index
d18Oc_uncertainty_test_table_df = d18Oc_uncertainty_test_table_df.set_index("d18Oc data from:")
# Function to format mean and std
def format_mean_std(mean, std):
return f"{mean:.3f} Β± {std:.3f}"
# Apply the formatting function to the mean and std columns
for month in months:
d18Oc_uncertainty_test_table_df[month] = d18Oc_uncertainty_test_table_df.apply(lambda row: format_mean_std(row[f"{month} Mean"], row[f"{month} Std"]), axis=1)
d18Oc_uncertainty_test_table_df = d18Oc_uncertainty_test_table_df.drop(columns=[f"{month} Mean", f"{month} Std"])
# Print the table
print(d18Oc_uncertainty_test_table_df.to_string())
# Export the table to a CSV file
d18Oc_uncertainty_test_table_df.to_csv("Sensitivity_test_D47_d18Oc_uncertainties/d18Oc_uncertainty_test_table.csv")
# Extract monthly means of measurements
mu_likelihood_monthly_d18Oc = Z_monthly_aggregated[len(mu_prior_d18Oc_monthly):2*len(mu_prior_d18Oc_monthly)].flatten()
std_likelihood_monthly_d18Oc = np.sqrt(np.diag(R_monthly_aggregated))[len(mu_prior_d18Oc_monthly):2*len(mu_prior_d18Oc_monthly)].flatten()
January February March April May June July August September October November December d18Oc data from: In situ measurements -1.627 Β± 0.529 -0.705 Β± 0.408 -0.447 Β± 0.530 -1.118 Β± 0.417 -1.801 Β± 0.334 -2.089 Β± 0.427 -2.144 Β± 0.485 -2.188 Β± 0.529 -2.272 Β± 0.588 -2.193 Β± 0.628 -2.202 Β± 0.682 -2.116 Β± 0.662 Individual Measurements -2.514 Β± 0.007 -1.511 Β± 0.006 -1.263 Β± 0.006 -1.359 Β± 0.008 -2.410 Β± 0.011 -3.850 Β± 0.012 -3.144 Β± 0.018 -4.103 Β± 0.016 -3.680 Β± 0.015 -2.835 Β± 0.014 -2.445 Β± 0.013 -3.243 Β± 0.010 Individual Measurements (SD D47 * 10) -2.513 Β± 0.008 -1.511 Β± 0.006 -1.265 Β± 0.006 -1.360 Β± 0.008 -2.410 Β± 0.011 -3.852 Β± 0.012 -3.125 Β± 0.018 -4.101 Β± 0.016 -3.698 Β± 0.015 -2.832 Β± 0.014 -2.448 Β± 0.013 -3.242 Β± 0.010 Individual Measurements (SD d18Oc * 10) -2.385 Β± 0.020 -1.590 Β± 0.016 -1.288 Β± 0.016 -1.403 Β± 0.021 -2.493 Β± 0.032 -3.435 Β± 0.029 -3.201 Β± 0.041 -3.745 Β± 0.040 -3.719 Β± 0.033 -3.414 Β± 0.032 -2.908 Β± 0.032 -3.226 Β± 0.029 Individual Measurements (both SDs * 10) -2.379 Β± 0.020 -1.592 Β± 0.016 -1.294 Β± 0.017 -1.411 Β± 0.021 -2.497 Β± 0.032 -3.432 Β± 0.029 -3.117 Β± 0.042 -3.730 Β± 0.040 -3.774 Β± 0.034 -3.400 Β± 0.032 -2.925 Β± 0.032 -3.228 Β± 0.029 Individual Measurements (SD D47 * 0.1) -2.520 Β± 0.007 -1.512 Β± 0.006 -1.255 Β± 0.006 -1.350 Β± 0.008 -2.412 Β± 0.011 -3.832 Β± 0.012 -3.266 Β± 0.017 -4.111 Β± 0.016 -3.570 Β± 0.014 -2.859 Β± 0.014 -2.428 Β± 0.013 -3.250 Β± 0.010 Individual Measurements (SD d18Oc * 0.1) -2.688 Β± 0.003 -1.342 Β± 0.002 -1.263 Β± 0.002 -1.557 Β± 0.003 -2.260 Β± 0.004 -3.826 Β± 0.005 -3.303 Β± 0.006 -4.224 Β± 0.006 -3.910 Β± 0.006 -2.378 Β± 0.005 -2.259 Β± 0.005 -3.212 Β± 0.003 Individual Measurements (both SDs * 0.1) -2.687 Β± 0.003 -1.349 Β± 0.002 -1.257 Β± 0.002 -1.550 Β± 0.003 -2.266 Β± 0.003 -3.809 Β± 0.005 -3.331 Β± 0.006 -4.235 Β± 0.006 -3.864 Β± 0.005 -2.381 Β± 0.005 -2.250 Β± 0.005 -3.221 Β± 0.003 Aggregated Measurements -2.747 Β± 0.115 -2.452 Β± 0.123 -2.274 Β± 0.109 -2.469 Β± 0.101 -2.909 Β± 0.108 -3.278 Β± 0.079 -3.099 Β± 0.067 -3.072 Β± 0.075 -3.060 Β± 0.076 -2.894 Β± 0.066 -2.844 Β± 0.071 -2.851 Β± 0.088 Aggregated Measurements (SD D47 * 10) -2.747 Β± 0.115 -2.451 Β± 0.123 -2.274 Β± 0.109 -2.471 Β± 0.101 -2.914 Β± 0.108 -3.281 Β± 0.079 -3.092 Β± 0.067 -3.072 Β± 0.075 -3.065 Β± 0.076 -2.892 Β± 0.066 -2.844 Β± 0.071 -2.853 Β± 0.088 Aggregated Measurements (SD d18Oc * 10) -2.508 Β± 0.192 -2.068 Β± 0.166 -1.900 Β± 0.144 -2.004 Β± 0.145 -2.512 Β± 0.185 -2.958 Β± 0.191 -2.958 Β± 0.160 -3.051 Β± 0.179 -3.209 Β± 0.183 -3.183 Β± 0.164 -3.085 Β± 0.172 -2.981 Β± 0.210 Aggregated Measurements (both SDs * 10) -2.508 Β± 0.192 -2.068 Β± 0.166 -1.900 Β± 0.144 -2.008 Β± 0.145 -2.518 Β± 0.185 -2.961 Β± 0.191 -2.948 Β± 0.161 -3.047 Β± 0.179 -3.211 Β± 0.183 -3.179 Β± 0.164 -3.083 Β± 0.172 -2.982 Β± 0.210 Aggregated Measurements (SD D47 * 0.1) -2.744 Β± 0.115 -2.457 Β± 0.123 -2.277 Β± 0.109 -2.450 Β± 0.101 -2.861 Β± 0.108 -3.248 Β± 0.078 -3.158 Β± 0.065 -3.079 Β± 0.075 -3.017 Β± 0.076 -2.903 Β± 0.066 -2.835 Β± 0.071 -2.830 Β± 0.088 Aggregated Measurements (SD d18Oc * 0.1) -2.353 Β± 0.073 -2.398 Β± 0.082 -2.287 Β± 0.068 -2.726 Β± 0.060 -3.350 Β± 0.077 -3.525 Β± 0.030 -3.439 Β± 0.032 -3.586 Β± 0.036 -3.076 Β± 0.037 -2.390 Β± 0.026 -2.234 Β± 0.037 -2.372 Β± 0.051 Aggregated Measurements (both SDs * 0.1) -2.356 Β± 0.073 -2.398 Β± 0.081 -2.286 Β± 0.067 -2.715 Β± 0.060 -3.332 Β± 0.077 -3.517 Β± 0.030 -3.465 Β± 0.031 -3.579 Β± 0.036 -3.045 Β± 0.037 -2.393 Β± 0.026 -2.226 Β± 0.037 -2.371 Β± 0.051
Plot results of uncertainty test with increased uncertainty on D47 (SST) assimilationΒΆ
InΒ [46]:
# Plot prior, likelihood and posterior distributions for D47 based on individual measurements
fig, axes = plt.subplots(4, 2, figsize=(20, 25))
fig.suptitle('Sensitivity Test: Effect of increased D47 and d18Oc Uncertainties on SST-D47', fontsize=24, fontweight='bold') # Provide title for the entire figure
plt.subplots_adjust(top=0.95) # Adjust top spacing to fit the title
plt.subplots_adjust(hspace=0.3) # Adjust vertical spacing between subplots to prevent overlap between titles and x-axis labels
# -- PANEL 1 -- DATA BASED ON INDIVIDUAL MEASUREMENTS
# Calculate the updated seasonal posterior for SST_D47
std_post_SST_D47_monthlySC = np.sqrt(np.diag(cov_post_SST_D47_monthlySC))
std_prior_SST_D47_monthly_original = np.sqrt(np.diag(cov_prior_SST_D47_monthly_original))
# PRIOR
axes[0, 0].plot(months_scale, mu_prior_SST_D47_monthly_original, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[0, 0].fill_between(
months_scale,
mu_prior_SST_D47_monthly_original - 2 * std_prior_SST_D47_monthly_original,
mu_prior_SST_D47_monthly_original + 2 * std_prior_SST_D47_monthly_original,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[0, 0].plot(months_scale, mu_likelihood_monthly_D47, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[0, 0].fill_between(
months_scale,
mu_likelihood_monthly_D47 - 2 * std_likelihood_monthly_D47,
mu_likelihood_monthly_D47 + 2 * std_likelihood_monthly_D47,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[0, 0].plot(months_scale, mu_post_SST_D47_monthlySC, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[0, 0].fill_between(
months_scale,
mu_post_SST_D47_monthlySC - 2 * std_post_SST_D47_monthlySC,
mu_post_SST_D47_monthlySC + 2 * std_post_SST_D47_monthlySC,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[0, 0].plot(months_scale, mu_measurement_SST_D47_monthly, label='Measurement Mean (SST)', color='b', marker='x')
axes[0, 0].fill_between(
months_scale,
mu_measurement_SST_D47_monthly - 2 * std_measurement_SST_D47_monthly,
mu_measurement_SST_D47_monthly + 2 * std_measurement_SST_D47_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
# Set x-ticks and labels
axes[0, 0].set_xticks(months_scale)
axes[0, 0].set_xticklabels(month_names, rotation=45, ha="right")
axes[0, 0].set_title('Based on individual measurements')
axes[0, 0].set_xlabel('Month')
axes[0, 0].set_ylabel('SST-D47 value (I-CDES)')
axes[0, 0].set_ylim(0.57, 0.63) # Set y-axis limits for better visibility
axes[0, 0].legend()
axes[0, 0].grid(True)
# -- PANEL 2 -- DATA BASED ON AGGREGATED MEASUREMENTS
# Calculate the updated seasonal posterior for SST_D47
std_post_SST_D47_monthly_aggregated = np.sqrt(np.diag(cov_post_SST_D47_monthly_aggregated))
# PRIOR
axes[0, 1].plot(months_scale, mu_prior_SST_D47_monthly_original, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[0, 1].fill_between(
months_scale,
mu_prior_SST_D47_monthly_original - 2 * std_prior_SST_D47_monthly_original,
mu_prior_SST_D47_monthly_original + 2 * std_prior_SST_D47_monthly_original,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[0, 1].plot(months_scale, mu_likelihood_monthly_D47, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[0, 1].fill_between(
months_scale,
mu_likelihood_monthly_D47 - 2 * std_likelihood_monthly_D47,
mu_likelihood_monthly_D47 + 2 * std_likelihood_monthly_D47,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[0, 1].plot(months_scale, mu_post_SST_D47_monthly_aggregated, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[0, 1].fill_between(
months_scale,
mu_post_SST_D47_monthly_aggregated - 2 * std_post_SST_D47_monthly_aggregated,
mu_post_SST_D47_monthly_aggregated + 2 * std_post_SST_D47_monthly_aggregated,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[0, 1].plot(months_scale, mu_measurement_SST_D47_monthly, label='Measurement Mean (SST)', color='b', marker='x')
axes[0, 1].fill_between(
months_scale,
mu_measurement_SST_D47_monthly - 2 * std_measurement_SST_D47_monthly,
mu_measurement_SST_D47_monthly + 2 * std_measurement_SST_D47_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
axes[0, 1].set_xticks(months_scale)
axes[0, 1].set_xticklabels(month_names, rotation=45, ha="right")
axes[0, 1].set_title('Based on aggregated measurements')
axes[0, 1].set_xlabel('Month')
axes[0, 1].set_ylabel('SST-D47 value (I-CDES)')
axes[0, 1].set_ylim(0.57, 0.63) # Set y-axis limits for better visibility
axes[0, 1].legend()
axes[0, 1].grid(True)
# -- PANEL 3 -- DATA BASED ON INDIVIDUAL MEASUREMENTS WITH HIGHER UNCERTAINTY ON BOTH D47 AND d18O
# Calculate the updated seasonal posterior for SST_D47
std_post_SST_D47_monthlySC_HU = np.sqrt(np.diag(cov_post_SST_D47_monthlySC_HU))
# PRIOR
axes[1, 0].plot(months_scale, mu_prior_SST_D47_monthly_original, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[1, 0].fill_between(
months_scale,
mu_prior_SST_D47_monthly_original - 2 * std_prior_SST_D47_monthly_original,
mu_prior_SST_D47_monthly_original + 2 * std_prior_SST_D47_monthly_original,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[1, 0].plot(months_scale, mu_likelihood_monthly_D47, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[1, 0].fill_between(
months_scale,
mu_likelihood_monthly_D47 - 2 * std_likelihood_monthly_D47,
mu_likelihood_monthly_D47 + 2 * std_likelihood_monthly_D47,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[1, 0].plot(months_scale, mu_post_SST_D47_monthlySC_HU, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[1, 0].fill_between(
months_scale,
mu_post_SST_D47_monthlySC_HU - 2 * std_post_SST_D47_monthlySC_HU,
mu_post_SST_D47_monthlySC_HU + 2 * std_post_SST_D47_monthlySC_HU,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[1, 0].plot(months_scale, mu_measurement_SST_D47_monthly, label='Measurement Mean (SST)', color='b', marker='x')
axes[1, 0].fill_between(
months_scale,
mu_measurement_SST_D47_monthly - 2 * std_measurement_SST_D47_monthly,
mu_measurement_SST_D47_monthly + 2 * std_measurement_SST_D47_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
axes[1, 0].set_xticks(months_scale)
axes[1, 0].set_xticklabels(month_names, rotation=45, ha="right")
axes[1, 0].set_title('Based on individual measurements with 10x higher uncertainty in both D47 and d18Oc')
axes[1, 0].set_xlabel('Month')
axes[1, 0].set_ylabel('SST-D47 value (I-CDES)')
axes[1, 0].set_ylim(0.57, 0.63) # Set y-axis limits for better visibility
axes[1, 0].legend()
axes[1, 0].grid(True)
# -- PANEL 4 -- DATA BASED ON AGGREGATED MEASUREMENTS WITH HIGHER UNCERTAINTY ON BOTH D47 AND d18O
# Calculate the updated seasonal posterior for SST_D47
std_post_SST_D47_monthly_aggregated_HU = np.sqrt(np.diag(cov_post_SST_D47_monthly_aggregated_HU))
# PRIOR
axes[1, 1].plot(months_scale, mu_prior_SST_D47_monthly_original, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[1, 1].fill_between(
months_scale,
mu_prior_SST_D47_monthly_original - 2 * std_prior_SST_D47_monthly_original,
mu_prior_SST_D47_monthly_original + 2 * std_prior_SST_D47_monthly_original,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[1, 1].plot(months_scale, mu_likelihood_monthly_D47, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[1, 1].fill_between(
months_scale,
mu_likelihood_monthly_D47 - 2 * std_likelihood_monthly_D47,
mu_likelihood_monthly_D47 + 2 * std_likelihood_monthly_D47,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[1, 1].plot(months_scale, mu_post_SST_D47_monthly_aggregated_HU, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[1, 1].fill_between(
months_scale,
mu_post_SST_D47_monthly_aggregated_HU - 2 * std_post_SST_D47_monthly_aggregated_HU,
mu_post_SST_D47_monthly_aggregated_HU + 2 * std_post_SST_D47_monthly_aggregated_HU,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[1, 1].plot(months_scale, mu_measurement_SST_D47_monthly, label='Measurement Mean (SST)', color='b', marker='x')
axes[1, 1].fill_between(
months_scale,
mu_measurement_SST_D47_monthly - 2 * std_measurement_SST_D47_monthly,
mu_measurement_SST_D47_monthly + 2 * std_measurement_SST_D47_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
axes[1, 1].set_xticks(months_scale)
axes[1, 1].set_xticklabels(month_names, rotation=45, ha="right")
axes[1, 1].set_title('Based on aggregated measurements with 10x higher uncertainty in both D47 and d18Oc')
axes[1, 1].set_xlabel('Month')
axes[1, 1].set_ylabel('SST-D47 value (I-CDES)')
axes[1, 1].set_ylim(0.57, 0.63) # Set y-axis limits for better visibility
axes[1, 1].legend()
axes[1, 1].grid(True)
# -- PANEL 5 -- DATA BASED ON INDIVIDUAL MEASUREMENTS WITH HIGHER UNCERTAINTY ON D47 ONLY
# Calculate the updated seasonal posterior for SST_D47
std_post_SST_D47_monthlySC_HU_D47 = np.sqrt(np.diag(cov_post_SST_D47_monthlySC_HU_D47))
# PRIOR
axes[2, 0].plot(months_scale, mu_prior_SST_D47_monthly_original, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[2, 0].fill_between(
months_scale,
mu_prior_SST_D47_monthly_original - 2 * std_prior_SST_D47_monthly_original,
mu_prior_SST_D47_monthly_original + 2 * std_prior_SST_D47_monthly_original,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[2, 0].plot(months_scale, mu_likelihood_monthly_D47, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[2, 0].fill_between(
months_scale,
mu_likelihood_monthly_D47 - 2 * std_likelihood_monthly_D47,
mu_likelihood_monthly_D47 + 2 * std_likelihood_monthly_D47,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[2, 0].plot(months_scale, mu_post_SST_D47_monthlySC_HU_D47, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[2, 0].fill_between(
months_scale,
mu_post_SST_D47_monthlySC_HU_D47 - 2 * std_post_SST_D47_monthlySC_HU_D47,
mu_post_SST_D47_monthlySC_HU_D47 + 2 * std_post_SST_D47_monthlySC_HU_D47,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[2, 0].plot(months_scale, mu_measurement_SST_D47_monthly, label='Measurement Mean (SST)', color='b', marker='x')
axes[2, 0].fill_between(
months_scale,
mu_measurement_SST_D47_monthly - 2 * std_measurement_SST_D47_monthly,
mu_measurement_SST_D47_monthly + 2 * std_measurement_SST_D47_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
axes[2, 0].set_xticks(months_scale)
axes[2, 0].set_xticklabels(month_names, rotation=45, ha="right")
axes[2, 0].set_title('Based on individual measurements with 10x higher uncertainty in D47 only')
axes[2, 0].set_xlabel('Month')
axes[2, 0].set_ylabel('SST-D47 value (I-CDES)')
axes[2, 0].set_ylim(0.57, 0.63) # Set y-axis limits for better visibility
axes[2, 0].legend()
axes[2, 0].grid(True)
# -- PANEL 6 -- DATA BASED ON AGGREGATED MEASUREMENTS WITH HIGHER UNCERTAINTY ON D47 ONLY
# Calculate the updated seasonal posterior for SST_D47
std_post_SST_D47_monthly_aggregated_HU_D47 = np.sqrt(np.diag(cov_post_SST_D47_monthly_aggregated_HU_D47))
# PRIOR
axes[2, 1].plot(months_scale, mu_prior_SST_D47_monthly_original, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[2, 1].fill_between(
months_scale,
mu_prior_SST_D47_monthly_original - 2 * std_prior_SST_D47_monthly_original,
mu_prior_SST_D47_monthly_original + 2 * std_prior_SST_D47_monthly_original,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[2, 1].plot(months_scale, mu_likelihood_monthly_D47, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[2, 1].fill_between(
months_scale,
mu_likelihood_monthly_D47 - 2 * std_likelihood_monthly_D47,
mu_likelihood_monthly_D47 + 2 * std_likelihood_monthly_D47,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[2, 1].plot(months_scale, mu_post_SST_D47_monthly_aggregated_HU_D47, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[2, 1].fill_between(
months_scale,
mu_post_SST_D47_monthly_aggregated_HU_D47 - 2 * std_post_SST_D47_monthly_aggregated_HU_D47,
mu_post_SST_D47_monthly_aggregated_HU_D47 + 2 * std_post_SST_D47_monthly_aggregated_HU_D47,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[2, 1].plot(months_scale, mu_measurement_SST_D47_monthly, label='Measurement Mean (SST)', color='b', marker='x')
axes[2, 1].fill_between(
months_scale,
mu_measurement_SST_D47_monthly - 2 * std_measurement_SST_D47_monthly,
mu_measurement_SST_D47_monthly + 2 * std_measurement_SST_D47_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
axes[2, 1].set_xticks(months_scale)
axes[2, 1].set_xticklabels(month_names, rotation=45, ha="right")
axes[2, 1].set_title('Based on aggregated measurements with 10x higher uncertainty in D47 only')
axes[2, 1].set_xlabel('Month')
axes[2, 1].set_ylabel('SST-D47 value (I-CDES)')
axes[2, 1].set_ylim(0.57, 0.63) # Set y-axis limits for better visibility
axes[2, 1].legend()
axes[2, 1].grid(True)
# -- PANEL 7 -- DATA BASED ON INDIVIDUAL MEASUREMENTS WITH HIGHER UNCERTAINTY ON d18Oc ONLY
# Calculate the updated seasonal posterior for D47
std_post_SST_D47_monthlySC_HU_d18O = np.sqrt(np.diag(cov_post_SST_D47_monthlySC_HU_d18O))
# PRIOR
axes[3, 0].plot(months_scale, mu_prior_SST_D47_monthly, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[3, 0].fill_between(
months_scale,
mu_prior_SST_D47_monthly - 2 * std_prior_SST_D47_monthly,
mu_prior_SST_D47_monthly + 2 * std_prior_SST_D47_monthly,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[3, 0].plot(months_scale, mu_likelihood_monthly_D47, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[3, 0].fill_between(
months_scale,
mu_likelihood_monthly_D47 - 2 * std_likelihood_monthly_D47,
mu_likelihood_monthly_D47 + 2 * std_likelihood_monthly_D47,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[3, 0].plot(months_scale, mu_post_SST_D47_monthlySC_HU_d18O, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[3, 0].fill_between(
months_scale,
mu_post_SST_D47_monthlySC_HU_d18O - 2 * std_post_SST_D47_monthlySC_HU_d18O,
mu_post_SST_D47_monthlySC_HU_d18O + 2 * std_post_SST_D47_monthlySC_HU_d18O,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[3, 0].plot(months_scale, mu_measurement_SST_D47_monthly, label='Measurement Mean (d18Oc)', color='b', marker='x')
axes[3, 0].fill_between(
months_scale,
mu_measurement_SST_D47_monthly - 2 * std_measurement_SST_D47_monthly,
mu_measurement_SST_D47_monthly + 2 * std_measurement_SST_D47_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
axes[3, 0].set_xticks(months_scale)
axes[3, 0].set_xticklabels(month_names, rotation=45, ha="right")
axes[3, 0].set_title('Based on individual measurements with 10x higher uncertainty in d18Oc only')
axes[3, 0].set_xlabel('Month')
axes[3, 0].set_ylabel('SST-D47 value (I-CDES)')
axes[3, 0].set_ylim(0.57, 0.63) # Set y-axis limits for better visibility
axes[3, 0].legend()
axes[3, 0].grid(True)
# -- PANEL 8 -- DATA BASED ON AGGREGATED MEASUREMENTS WITH HIGHER UNCERTAINTY ON d18Oc ONLY
# Calculate the updated seasonal posterior for D47
std_post_SST_D47_monthly_aggregated_HU_d18O = np.sqrt(np.diag(cov_post_SST_D47_monthly_aggregated_HU_d18O))
# PRIOR
axes[3, 1].plot(months_scale, mu_prior_SST_D47_monthly, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[3, 1].fill_between(
months_scale,
mu_prior_SST_D47_monthly - 2 * std_prior_SST_D47_monthly,
mu_prior_SST_D47_monthly + 2 * std_prior_SST_D47_monthly,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[3, 1].plot(months_scale, mu_likelihood_monthly_D47, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[3, 1].fill_between(
months_scale,
mu_likelihood_monthly_D47 - 2 * std_likelihood_monthly_D47,
mu_likelihood_monthly_D47 + 2 * std_likelihood_monthly_D47,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[3, 1].plot(months_scale, mu_post_SST_D47_monthly_aggregated_HU_d18O, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[3, 1].fill_between(
months_scale,
mu_post_SST_D47_monthly_aggregated_HU_d18O - 2 * std_post_SST_D47_monthly_aggregated_HU_d18O,
mu_post_SST_D47_monthly_aggregated_HU_d18O + 2 * std_post_SST_D47_monthly_aggregated_HU_d18O,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[3, 1].plot(months_scale, mu_measurement_SST_D47_monthly, label='Measurement Mean (d18Oc)', color='b', marker='x')
axes[3, 1].fill_between(
months_scale,
mu_measurement_SST_D47_monthly - 2 * std_measurement_SST_D47_monthly,
mu_measurement_SST_D47_monthly + 2 * std_measurement_SST_D47_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
axes[3, 1].set_xticks(months_scale)
axes[3, 1].set_xticklabels(month_names, rotation=45, ha="right")
axes[3, 1].set_title('Based on aggregated measurements with 10x higher uncertainty in d18Oc only')
axes[3, 1].set_xlabel('Month')
axes[3, 1].set_ylabel('SST-D47 value (I-CDES)')
axes[3, 1].set_ylim(0.57, 0.63) # Set y-axis limits for better visibility
axes[3, 1].legend()
axes[3, 1].grid(True)
# Show plot
plt.show()
Plot results of uncertainty test with reduced uncertainty on D47 (SST) assimilationΒΆ
InΒ [47]:
# Plot prior, likelihood and posterior distributions for D47 based on individual measurements
fig, axes = plt.subplots(4, 2, figsize=(20, 25))
fig.suptitle('Sensitivity Test: Effect of reduced D47 and d18Oc Uncertainties on SST-D47', fontsize=24, fontweight='bold') # Provide title for the entire figure
plt.subplots_adjust(top=0.95) # Adjust top spacing to fit the title
plt.subplots_adjust(hspace=0.3) # Adjust vertical spacing between subplots to prevent overlap between titles and x-axis labels
# -- PANEL 1 -- DATA BASED ON INDIVIDUAL MEASUREMENTS
# Calculate the updated seasonal posterior for SST_D47
std_post_SST_D47_monthlySC = np.sqrt(np.diag(cov_post_SST_D47_monthlySC))
std_prior_SST_D47_monthly_original = np.sqrt(np.diag(cov_prior_SST_D47_monthly_original))
# PRIOR
axes[0, 0].plot(months_scale, mu_prior_SST_D47_monthly_original, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[0, 0].fill_between(
months_scale,
mu_prior_SST_D47_monthly_original - 2 * std_prior_SST_D47_monthly_original,
mu_prior_SST_D47_monthly_original + 2 * std_prior_SST_D47_monthly_original,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[0, 0].plot(months_scale, mu_likelihood_monthly_D47, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[0, 0].fill_between(
months_scale,
mu_likelihood_monthly_D47 - 2 * std_likelihood_monthly_D47,
mu_likelihood_monthly_D47 + 2 * std_likelihood_monthly_D47,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[0, 0].plot(months_scale, mu_post_SST_D47_monthlySC, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[0, 0].fill_between(
months_scale,
mu_post_SST_D47_monthlySC - 2 * std_post_SST_D47_monthlySC,
mu_post_SST_D47_monthlySC + 2 * std_post_SST_D47_monthlySC,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[0, 0].plot(months_scale, mu_measurement_SST_D47_monthly, label='Measurement Mean (SST)', color='b', marker='x')
axes[0, 0].fill_between(
months_scale,
mu_measurement_SST_D47_monthly - 2 * std_measurement_SST_D47_monthly,
mu_measurement_SST_D47_monthly + 2 * std_measurement_SST_D47_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
# Set x-ticks and labels
axes[0, 0].set_xticks(months_scale)
axes[0, 0].set_xticklabels(month_names, rotation=45, ha="right")
axes[0, 0].set_title('Based on individual measurements')
axes[0, 0].set_xlabel('Month')
axes[0, 0].set_ylabel('SST-D47 value (I-CDES)')
axes[0, 0].set_ylim(0.54, 0.63) # Set y-axis limits for better visibility
axes[0, 0].legend()
axes[0, 0].grid(True)
# -- PANEL 2 -- DATA BASED ON AGGREGATED MEASUREMENTS
# Calculate the updated seasonal posterior for SST_D47
std_post_SST_D47_monthly_aggregated = np.sqrt(np.diag(cov_post_SST_D47_monthly_aggregated))
# PRIOR
axes[0, 1].plot(months_scale, mu_prior_SST_D47_monthly_original, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[0, 1].fill_between(
months_scale,
mu_prior_SST_D47_monthly_original - 2 * std_prior_SST_D47_monthly_original,
mu_prior_SST_D47_monthly_original + 2 * std_prior_SST_D47_monthly_original,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[0, 1].plot(months_scale, mu_likelihood_monthly_D47, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[0, 1].fill_between(
months_scale,
mu_likelihood_monthly_D47 - 2 * std_likelihood_monthly_D47,
mu_likelihood_monthly_D47 + 2 * std_likelihood_monthly_D47,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[0, 1].plot(months_scale, mu_post_SST_D47_monthly_aggregated, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[0, 1].fill_between(
months_scale,
mu_post_SST_D47_monthly_aggregated - 2 * std_post_SST_D47_monthly_aggregated,
mu_post_SST_D47_monthly_aggregated + 2 * std_post_SST_D47_monthly_aggregated,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[0, 1].plot(months_scale, mu_measurement_SST_D47_monthly, label='Measurement Mean (SST)', color='b', marker='x')
axes[0, 1].fill_between(
months_scale,
mu_measurement_SST_D47_monthly - 2 * std_measurement_SST_D47_monthly,
mu_measurement_SST_D47_monthly + 2 * std_measurement_SST_D47_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
axes[0, 1].set_xticks(months_scale)
axes[0, 1].set_xticklabels(month_names, rotation=45, ha="right")
axes[0, 1].set_title('Based on aggregated measurements')
axes[0, 1].set_xlabel('Month')
axes[0, 1].set_ylabel('SST-D47 value (I-CDES)')
axes[0, 1].set_ylim(0.54, 0.63) # Set y-axis limits for better visibility
axes[0, 1].legend()
axes[0, 1].grid(True)
# -- PANEL 3 -- DATA BASED ON INDIVIDUAL MEASUREMENTS WITH HIGHER UNCERTAINTY ON BOTH D47 AND d18O
# Calculate the updated seasonal posterior for SST_D47
std_post_SST_D47_monthlySC_LU = np.sqrt(np.diag(cov_post_SST_D47_monthlySC_LU))
# PRIOR
axes[1, 0].plot(months_scale, mu_prior_SST_D47_monthly_original, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[1, 0].fill_between(
months_scale,
mu_prior_SST_D47_monthly_original - 2 * std_prior_SST_D47_monthly_original,
mu_prior_SST_D47_monthly_original + 2 * std_prior_SST_D47_monthly_original,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[1, 0].plot(months_scale, mu_likelihood_monthly_D47, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[1, 0].fill_between(
months_scale,
mu_likelihood_monthly_D47 - 2 * std_likelihood_monthly_D47,
mu_likelihood_monthly_D47 + 2 * std_likelihood_monthly_D47,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[1, 0].plot(months_scale, mu_post_SST_D47_monthlySC_LU, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[1, 0].fill_between(
months_scale,
mu_post_SST_D47_monthlySC_LU - 2 * std_post_SST_D47_monthlySC_LU,
mu_post_SST_D47_monthlySC_LU + 2 * std_post_SST_D47_monthlySC_LU,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[1, 0].plot(months_scale, mu_measurement_SST_D47_monthly, label='Measurement Mean (SST)', color='b', marker='x')
axes[1, 0].fill_between(
months_scale,
mu_measurement_SST_D47_monthly - 2 * std_measurement_SST_D47_monthly,
mu_measurement_SST_D47_monthly + 2 * std_measurement_SST_D47_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
axes[1, 0].set_xticks(months_scale)
axes[1, 0].set_xticklabels(month_names, rotation=45, ha="right")
axes[1, 0].set_title('Based on individual measurements with 10x lower uncertainty in both D47 and d18Oc')
axes[1, 0].set_xlabel('Month')
axes[1, 0].set_ylabel('SST-D47 value (I-CDES)')
axes[1, 0].set_ylim(0.54, 0.63) # Set y-axis limits for better visibility
axes[1, 0].legend()
axes[1, 0].grid(True)
# -- PANEL 4 -- DATA BASED ON AGGREGATED MEASUREMENTS WITH HIGHER UNCERTAINTY ON BOTH D47 AND d18O
# Calculate the updated seasonal posterior for SST_D47
std_post_SST_D47_monthly_aggregated_LU = np.sqrt(np.diag(cov_post_SST_D47_monthly_aggregated_LU))
# PRIOR
axes[1, 1].plot(months_scale, mu_prior_SST_D47_monthly_original, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[1, 1].fill_between(
months_scale,
mu_prior_SST_D47_monthly_original - 2 * std_prior_SST_D47_monthly_original,
mu_prior_SST_D47_monthly_original + 2 * std_prior_SST_D47_monthly_original,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[1, 1].plot(months_scale, mu_likelihood_monthly_D47, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[1, 1].fill_between(
months_scale,
mu_likelihood_monthly_D47 - 2 * std_likelihood_monthly_D47,
mu_likelihood_monthly_D47 + 2 * std_likelihood_monthly_D47,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[1, 1].plot(months_scale, mu_post_SST_D47_monthly_aggregated_LU, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[1, 1].fill_between(
months_scale,
mu_post_SST_D47_monthly_aggregated_LU - 2 * std_post_SST_D47_monthly_aggregated_LU,
mu_post_SST_D47_monthly_aggregated_LU + 2 * std_post_SST_D47_monthly_aggregated_LU,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[1, 1].plot(months_scale, mu_measurement_SST_D47_monthly, label='Measurement Mean (SST)', color='b', marker='x')
axes[1, 1].fill_between(
months_scale,
mu_measurement_SST_D47_monthly - 2 * std_measurement_SST_D47_monthly,
mu_measurement_SST_D47_monthly + 2 * std_measurement_SST_D47_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
axes[1, 1].set_xticks(months_scale)
axes[1, 1].set_xticklabels(month_names, rotation=45, ha="right")
axes[1, 1].set_title('Based on aggregated measurements with 10x lower uncertainty in both D47 and d18Oc')
axes[1, 1].set_xlabel('Month')
axes[1, 1].set_ylabel('SST-D47 value (I-CDES)')
axes[1, 1].set_ylim(0.54, 0.63) # Set y-axis limits for better visibility
axes[1, 1].legend()
axes[1, 1].grid(True)
# -- PANEL 5 -- DATA BASED ON INDIVIDUAL MEASUREMENTS WITH HIGHER UNCERTAINTY ON D47 ONLY
# Calculate the updated seasonal posterior for SST_D47
std_post_SST_D47_monthlySC_LU_D47 = np.sqrt(np.diag(cov_post_SST_D47_monthlySC_LU_D47))
# PRIOR
axes[2, 0].plot(months_scale, mu_prior_SST_D47_monthly_original, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[2, 0].fill_between(
months_scale,
mu_prior_SST_D47_monthly_original - 2 * std_prior_SST_D47_monthly_original,
mu_prior_SST_D47_monthly_original + 2 * std_prior_SST_D47_monthly_original,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[2, 0].plot(months_scale, mu_likelihood_monthly_D47, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[2, 0].fill_between(
months_scale,
mu_likelihood_monthly_D47 - 2 * std_likelihood_monthly_D47,
mu_likelihood_monthly_D47 + 2 * std_likelihood_monthly_D47,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[2, 0].plot(months_scale, mu_post_SST_D47_monthlySC_LU_D47, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[2, 0].fill_between(
months_scale,
mu_post_SST_D47_monthlySC_LU_D47 - 2 * std_post_SST_D47_monthlySC_LU_D47,
mu_post_SST_D47_monthlySC_LU_D47 + 2 * std_post_SST_D47_monthlySC_LU_D47,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[2, 0].plot(months_scale, mu_measurement_SST_D47_monthly, label='Measurement Mean (SST)', color='b', marker='x')
axes[2, 0].fill_between(
months_scale,
mu_measurement_SST_D47_monthly - 2 * std_measurement_SST_D47_monthly,
mu_measurement_SST_D47_monthly + 2 * std_measurement_SST_D47_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
axes[2, 0].set_xticks(months_scale)
axes[2, 0].set_xticklabels(month_names, rotation=45, ha="right")
axes[2, 0].set_title('Based on individual measurements with 10x lower uncertainty in D47 only')
axes[2, 0].set_xlabel('Month')
axes[2, 0].set_ylabel('SST-D47 value (I-CDES)')
axes[2, 0].set_ylim(0.54, 0.63) # Set y-axis limits for better visibility
axes[2, 0].legend()
axes[2, 0].grid(True)
# -- PANEL 6 -- DATA BASED ON AGGREGATED MEASUREMENTS WITH HIGHER UNCERTAINTY ON D47 ONLY
# Calculate the updated seasonal posterior for SST_D47
std_post_SST_D47_monthly_aggregated_LU_D47 = np.sqrt(np.diag(cov_post_SST_D47_monthly_aggregated_LU_D47))
# PRIOR
axes[2, 1].plot(months_scale, mu_prior_SST_D47_monthly_original, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[2, 1].fill_between(
months_scale,
mu_prior_SST_D47_monthly_original - 2 * std_prior_SST_D47_monthly_original,
mu_prior_SST_D47_monthly_original + 2 * std_prior_SST_D47_monthly_original,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[2, 1].plot(months_scale, mu_likelihood_monthly_D47, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[2, 1].fill_between(
months_scale,
mu_likelihood_monthly_D47 - 2 * std_likelihood_monthly_D47,
mu_likelihood_monthly_D47 + 2 * std_likelihood_monthly_D47,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[2, 1].plot(months_scale, mu_post_SST_D47_monthly_aggregated_LU_D47, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[2, 1].fill_between(
months_scale,
mu_post_SST_D47_monthly_aggregated_LU_D47 - 2 * std_post_SST_D47_monthly_aggregated_LU_D47,
mu_post_SST_D47_monthly_aggregated_LU_D47 + 2 * std_post_SST_D47_monthly_aggregated_LU_D47,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[2, 1].plot(months_scale, mu_measurement_SST_D47_monthly, label='Measurement Mean (SST)', color='b', marker='x')
axes[2, 1].fill_between(
months_scale,
mu_measurement_SST_D47_monthly - 2 * std_measurement_SST_D47_monthly,
mu_measurement_SST_D47_monthly + 2 * std_measurement_SST_D47_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
axes[2, 1].set_xticks(months_scale)
axes[2, 1].set_xticklabels(month_names, rotation=45, ha="right")
axes[2, 1].set_title('Based on aggregated measurements with 10x lower uncertainty in D47 only')
axes[2, 1].set_xlabel('Month')
axes[2, 1].set_ylabel('SST-D47 value (I-CDES)')
axes[2, 1].set_ylim(0.54, 0.63) # Set y-axis limits for better visibility
axes[2, 1].legend()
axes[2, 1].grid(True)
# -- PANEL 7 -- DATA BASED ON INDIVIDUAL MEASUREMENTS WITH HIGHER UNCERTAINTY ON d18Oc ONLY
# Calculate the updated seasonal posterior for D47
std_post_SST_D47_monthlySC_LU_d18O = np.sqrt(np.diag(cov_post_SST_D47_monthlySC_LU_d18O))
# PRIOR
axes[3, 0].plot(months_scale, mu_prior_SST_D47_monthly, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[3, 0].fill_between(
months_scale,
mu_prior_SST_D47_monthly - 2 * std_prior_SST_D47_monthly,
mu_prior_SST_D47_monthly + 2 * std_prior_SST_D47_monthly,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[3, 0].plot(months_scale, mu_likelihood_monthly_D47, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[3, 0].fill_between(
months_scale,
mu_likelihood_monthly_D47 - 2 * std_likelihood_monthly_D47,
mu_likelihood_monthly_D47 + 2 * std_likelihood_monthly_D47,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[3, 0].plot(months_scale, mu_post_SST_D47_monthlySC_LU_d18O, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[3, 0].fill_between(
months_scale,
mu_post_SST_D47_monthlySC_LU_d18O - 2 * std_post_SST_D47_monthlySC_LU_d18O,
mu_post_SST_D47_monthlySC_LU_d18O + 2 * std_post_SST_D47_monthlySC_LU_d18O,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[3, 0].plot(months_scale, mu_measurement_SST_D47_monthly, label='Measurement Mean (d18Oc)', color='b', marker='x')
axes[3, 0].fill_between(
months_scale,
mu_measurement_SST_D47_monthly - 2 * std_measurement_SST_D47_monthly,
mu_measurement_SST_D47_monthly + 2 * std_measurement_SST_D47_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
axes[3, 0].set_xticks(months_scale)
axes[3, 0].set_xticklabels(month_names, rotation=45, ha="right")
axes[3, 0].set_title('Based on individual measurements with 10x lower uncertainty in d18Oc only')
axes[3, 0].set_xlabel('Month')
axes[3, 0].set_ylabel('SST-D47 value (I-CDES)')
axes[3, 0].set_ylim(0.54, 0.63) # Set y-axis limits for better visibility
axes[3, 0].legend()
axes[3, 0].grid(True)
# -- PANEL 8 -- DATA BASED ON AGGREGATED MEASUREMENTS WITH HIGHER UNCERTAINTY ON d18Oc ONLY
# Calculate the updated seasonal posterior for D47
std_post_SST_D47_monthly_aggregated_LU_d18O = np.sqrt(np.diag(cov_post_SST_D47_monthly_aggregated_LU_d18O))
# PRIOR
axes[3, 1].plot(months_scale, mu_prior_SST_D47_monthly, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[3, 1].fill_between(
months_scale,
mu_prior_SST_D47_monthly - 2 * std_prior_SST_D47_monthly,
mu_prior_SST_D47_monthly + 2 * std_prior_SST_D47_monthly,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[3, 1].plot(months_scale, mu_likelihood_monthly_D47, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[3, 1].fill_between(
months_scale,
mu_likelihood_monthly_D47 - 2 * std_likelihood_monthly_D47,
mu_likelihood_monthly_D47 + 2 * std_likelihood_monthly_D47,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[3, 1].plot(months_scale, mu_post_SST_D47_monthly_aggregated_LU_d18O, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[3, 1].fill_between(
months_scale,
mu_post_SST_D47_monthly_aggregated_LU_d18O - 2 * std_post_SST_D47_monthly_aggregated_LU_d18O,
mu_post_SST_D47_monthly_aggregated_LU_d18O + 2 * std_post_SST_D47_monthly_aggregated_LU_d18O,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[3, 1].plot(months_scale, mu_measurement_SST_D47_monthly, label='Measurement Mean (d18Oc)', color='b', marker='x')
axes[3, 1].fill_between(
months_scale,
mu_measurement_SST_D47_monthly - 2 * std_measurement_SST_D47_monthly,
mu_measurement_SST_D47_monthly + 2 * std_measurement_SST_D47_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
axes[3, 1].set_xticks(months_scale)
axes[3, 1].set_xticklabels(month_names, rotation=45, ha="right")
axes[3, 1].set_title('Based on aggregated measurements with 10x lower uncertainty in d18Oc only')
axes[3, 1].set_xlabel('Month')
axes[3, 1].set_ylabel('SST-D47 value (I-CDES)')
axes[3, 1].set_ylim(0.54, 0.63) # Set y-axis limits for better visibility
axes[3, 1].legend()
axes[3, 1].grid(True)
# Show plot
plt.show()
Plot results of uncertainty test with increased uncertainty on d18Oc assimilationΒΆ
InΒ [48]:
# Plot prior, likelihood and posterior distributions for D47 based on individual measurements
fig, axes = plt.subplots(4, 2, figsize=(20, 25))
fig.suptitle('Sensitivity Test: Effect of increased D47 and d18Oc Uncertainties on d18Oc', fontsize=24, fontweight='bold') # Provide title for the entire figure
plt.subplots_adjust(top=0.95) # Adjust top spacing to fit the title
plt.subplots_adjust(hspace=0.3) # Adjust vertical spacing between subplots to prevent overlap between titles and x-axis labels
# -- PANEL 1 -- DATA BASED ON INDIVIDUAL MEASUREMENTS
# Calculate the updated seasonal posterior for SST_D47
std_post_d18Oc_monthlySC = np.sqrt(np.diag(cov_post_d18Oc_monthlySC))
std_prior_d18Oc_monthly_original = np.sqrt(np.diag(cov_prior_d18Oc_monthly_original))
# PRIOR
axes[0, 0].plot(months_scale, mu_prior_d18Oc_monthly_original, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[0, 0].fill_between(
months_scale,
mu_prior_d18Oc_monthly_original - 2 * std_prior_d18Oc_monthly_original,
mu_prior_d18Oc_monthly_original + 2 * std_prior_d18Oc_monthly_original,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[0, 0].plot(months_scale, mu_likelihood_monthly_d18Oc, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[0, 0].fill_between(
months_scale,
mu_likelihood_monthly_d18Oc - 2 * std_likelihood_monthly_d18Oc,
mu_likelihood_monthly_d18Oc + 2 * std_likelihood_monthly_d18Oc,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[0, 0].plot(months_scale, mu_post_d18Oc_monthlySC, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[0, 0].fill_between(
months_scale,
mu_post_d18Oc_monthlySC - 2 * std_post_d18Oc_monthlySC,
mu_post_d18Oc_monthlySC + 2 * std_post_d18Oc_monthlySC,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[0, 0].plot(months_scale, mu_measurement_d18Oc_monthly, label='Measurement Mean (SST)', color='b', marker='x')
axes[0, 0].fill_between(
months_scale,
mu_measurement_d18Oc_monthly - 2 * std_measurement_d18Oc_monthly,
mu_measurement_d18Oc_monthly + 2 * std_measurement_d18Oc_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
# Set x-ticks and labels
axes[0, 0].set_xticks(months_scale)
axes[0, 0].set_xticklabels(month_names, rotation=45, ha="right")
axes[0, 0].set_title('Based on individual measurements')
axes[0, 0].set_xlabel('Month')
axes[0, 0].set_ylabel('d18Oc value (VPDB)')
# axes[0, 0].set_ylim(0.57, 0.63) # Set y-axis limits for better visibility
axes[0, 0].legend()
axes[0, 0].grid(True)
# -- PANEL 2 -- DATA BASED ON AGGREGATED MEASUREMENTS
# Calculate the updated seasonal posterior for SST_D47
std_post_d18Oc_monthly_aggregated = np.sqrt(np.diag(cov_post_d18Oc_monthly_aggregated))
# PRIOR
axes[0, 1].plot(months_scale, mu_prior_d18Oc_monthly_original, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[0, 1].fill_between(
months_scale,
mu_prior_d18Oc_monthly_original - 2 * std_prior_d18Oc_monthly_original,
mu_prior_d18Oc_monthly_original + 2 * std_prior_d18Oc_monthly_original,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[0, 1].plot(months_scale, mu_likelihood_monthly_d18Oc, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[0, 1].fill_between(
months_scale,
mu_likelihood_monthly_d18Oc - 2 * std_likelihood_monthly_d18Oc,
mu_likelihood_monthly_d18Oc + 2 * std_likelihood_monthly_d18Oc,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[0, 1].plot(months_scale, mu_post_d18Oc_monthly_aggregated, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[0, 1].fill_between(
months_scale,
mu_post_d18Oc_monthly_aggregated - 2 * std_post_d18Oc_monthly_aggregated,
mu_post_d18Oc_monthly_aggregated + 2 * std_post_d18Oc_monthly_aggregated,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[0, 1].plot(months_scale, mu_measurement_d18Oc_monthly, label='Measurement Mean (SST)', color='b', marker='x')
axes[0, 1].fill_between(
months_scale,
mu_measurement_d18Oc_monthly - 2 * std_measurement_d18Oc_monthly,
mu_measurement_d18Oc_monthly + 2 * std_measurement_d18Oc_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
axes[0, 1].set_xticks(months_scale)
axes[0, 1].set_xticklabels(month_names, rotation=45, ha="right")
axes[0, 1].set_title('Based on aggregated measurements')
axes[0, 1].set_xlabel('Month')
axes[0, 1].set_ylabel('d18Oc value (VPDB)')
# axes[0, 1].set_ylim(0.57, 0.63) # Set y-axis limits for better visibility
axes[0, 1].legend()
axes[0, 1].grid(True)
# -- PANEL 3 -- DATA BASED ON INDIVIDUAL MEASUREMENTS WITH HIGHER UNCERTAINTY ON BOTH D47 AND d18O
# Calculate the updated seasonal posterior for SST_D47
std_post_d18Oc_monthlySC_HU = np.sqrt(np.diag(cov_post_d18Oc_monthlySC_HU))
# PRIOR
axes[1, 0].plot(months_scale, mu_prior_d18Oc_monthly_original, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[1, 0].fill_between(
months_scale,
mu_prior_d18Oc_monthly_original - 2 * std_prior_d18Oc_monthly_original,
mu_prior_d18Oc_monthly_original + 2 * std_prior_d18Oc_monthly_original,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[1, 0].plot(months_scale, mu_likelihood_monthly_d18Oc, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[1, 0].fill_between(
months_scale,
mu_likelihood_monthly_d18Oc - 2 * std_likelihood_monthly_d18Oc,
mu_likelihood_monthly_d18Oc + 2 * std_likelihood_monthly_d18Oc,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[1, 0].plot(months_scale, mu_post_d18Oc_monthlySC_HU, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[1, 0].fill_between(
months_scale,
mu_post_d18Oc_monthlySC_HU - 2 * std_post_d18Oc_monthlySC_HU,
mu_post_d18Oc_monthlySC_HU + 2 * std_post_d18Oc_monthlySC_HU,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[1, 0].plot(months_scale, mu_measurement_d18Oc_monthly, label='Measurement Mean (SST)', color='b', marker='x')
axes[1, 0].fill_between(
months_scale,
mu_measurement_d18Oc_monthly - 2 * std_measurement_d18Oc_monthly,
mu_measurement_d18Oc_monthly + 2 * std_measurement_d18Oc_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
axes[1, 0].set_xticks(months_scale)
axes[1, 0].set_xticklabels(month_names, rotation=45, ha="right")
axes[1, 0].set_title('Based on individual measurements with 10x higher uncertainty in both D47 and d18Oc')
axes[1, 0].set_xlabel('Month')
axes[1, 0].set_ylabel('d18Oc value (VPDB)')
# axes[1, 0].set_ylim(0.57, 0.63) # Set y-axis limits for better visibility
axes[1, 0].legend()
axes[1, 0].grid(True)
# -- PANEL 4 -- DATA BASED ON AGGREGATED MEASUREMENTS WITH HIGHER UNCERTAINTY ON BOTH D47 AND d18O
# Calculate the updated seasonal posterior for SST_D47
std_post_d18Oc_monthly_aggregated_HU = np.sqrt(np.diag(cov_post_d18Oc_monthly_aggregated_HU))
# PRIOR
axes[1, 1].plot(months_scale, mu_prior_d18Oc_monthly_original, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[1, 1].fill_between(
months_scale,
mu_prior_d18Oc_monthly_original - 2 * std_prior_d18Oc_monthly_original,
mu_prior_d18Oc_monthly_original + 2 * std_prior_d18Oc_monthly_original,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[1, 1].plot(months_scale, mu_likelihood_monthly_d18Oc, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[1, 1].fill_between(
months_scale,
mu_likelihood_monthly_d18Oc - 2 * std_likelihood_monthly_d18Oc,
mu_likelihood_monthly_d18Oc + 2 * std_likelihood_monthly_d18Oc,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[1, 1].plot(months_scale, mu_post_d18Oc_monthly_aggregated_HU, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[1, 1].fill_between(
months_scale,
mu_post_d18Oc_monthly_aggregated_HU - 2 * std_post_d18Oc_monthly_aggregated_HU,
mu_post_d18Oc_monthly_aggregated_HU + 2 * std_post_d18Oc_monthly_aggregated_HU,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[1, 1].plot(months_scale, mu_measurement_d18Oc_monthly, label='Measurement Mean (SST)', color='b', marker='x')
axes[1, 1].fill_between(
months_scale,
mu_measurement_d18Oc_monthly - 2 * std_measurement_d18Oc_monthly,
mu_measurement_d18Oc_monthly + 2 * std_measurement_d18Oc_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
axes[1, 1].set_xticks(months_scale)
axes[1, 1].set_xticklabels(month_names, rotation=45, ha="right")
axes[1, 1].set_title('Based on aggregated measurements with 10x higher uncertainty in both D47 and d18Oc')
axes[1, 1].set_xlabel('Month')
axes[1, 1].set_ylabel('d18Oc value (VPDB)')
# axes[1, 1].set_ylim(0.57, 0.63) # Set y-axis limits for better visibility
axes[1, 1].legend()
axes[1, 1].grid(True)
# -- PANEL 5 -- DATA BASED ON INDIVIDUAL MEASUREMENTS WITH HIGHER UNCERTAINTY ON D47 ONLY
# Calculate the updated seasonal posterior for SST_D47
std_post_d18Oc_monthlySC_HU_D47 = np.sqrt(np.diag(cov_post_d18Oc_monthlySC_HU_D47))
# PRIOR
axes[2, 0].plot(months_scale, mu_prior_d18Oc_monthly_original, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[2, 0].fill_between(
months_scale,
mu_prior_d18Oc_monthly_original - 2 * std_prior_d18Oc_monthly_original,
mu_prior_d18Oc_monthly_original + 2 * std_prior_d18Oc_monthly_original,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[2, 0].plot(months_scale, mu_likelihood_monthly_d18Oc, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[2, 0].fill_between(
months_scale,
mu_likelihood_monthly_d18Oc - 2 * std_likelihood_monthly_d18Oc,
mu_likelihood_monthly_d18Oc + 2 * std_likelihood_monthly_d18Oc,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[2, 0].plot(months_scale, mu_post_d18Oc_monthlySC_HU_D47, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[2, 0].fill_between(
months_scale,
mu_post_d18Oc_monthlySC_HU_D47 - 2 * std_post_d18Oc_monthlySC_HU_D47,
mu_post_d18Oc_monthlySC_HU_D47 + 2 * std_post_d18Oc_monthlySC_HU_D47,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[2, 0].plot(months_scale, mu_measurement_d18Oc_monthly, label='Measurement Mean (SST)', color='b', marker='x')
axes[2, 0].fill_between(
months_scale,
mu_measurement_d18Oc_monthly - 2 * std_measurement_d18Oc_monthly,
mu_measurement_d18Oc_monthly + 2 * std_measurement_d18Oc_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
axes[2, 0].set_xticks(months_scale)
axes[2, 0].set_xticklabels(month_names, rotation=45, ha="right")
axes[2, 0].set_title('Based on individual measurements with 10x higher uncertainty in D47 only')
axes[2, 0].set_xlabel('Month')
axes[2, 0].set_ylabel('d18Oc value (VPDB)')
# axes[2, 0].set_ylim(0.57, 0.63) # Set y-axis limits for better visibility
axes[2, 0].legend()
axes[2, 0].grid(True)
# -- PANEL 6 -- DATA BASED ON AGGREGATED MEASUREMENTS WITH HIGHER UNCERTAINTY ON D47 ONLY
# Calculate the updated seasonal posterior for SST_D47
std_post_d18Oc_monthly_aggregated_HU_D47 = np.sqrt(np.diag(cov_post_d18Oc_monthly_aggregated_HU_D47))
# PRIOR
axes[2, 1].plot(months_scale, mu_prior_d18Oc_monthly_original, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[2, 1].fill_between(
months_scale,
mu_prior_d18Oc_monthly_original - 2 * std_prior_d18Oc_monthly_original,
mu_prior_d18Oc_monthly_original + 2 * std_prior_d18Oc_monthly_original,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[2, 1].plot(months_scale, mu_likelihood_monthly_d18Oc, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[2, 1].fill_between(
months_scale,
mu_likelihood_monthly_d18Oc - 2 * std_likelihood_monthly_d18Oc,
mu_likelihood_monthly_d18Oc + 2 * std_likelihood_monthly_d18Oc,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[2, 1].plot(months_scale, mu_post_d18Oc_monthly_aggregated_HU_D47, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[2, 1].fill_between(
months_scale,
mu_post_d18Oc_monthly_aggregated_HU_D47 - 2 * std_post_d18Oc_monthly_aggregated_HU_D47,
mu_post_d18Oc_monthly_aggregated_HU_D47 + 2 * std_post_d18Oc_monthly_aggregated_HU_D47,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[2, 1].plot(months_scale, mu_measurement_d18Oc_monthly, label='Measurement Mean (SST)', color='b', marker='x')
axes[2, 1].fill_between(
months_scale,
mu_measurement_d18Oc_monthly - 2 * std_measurement_d18Oc_monthly,
mu_measurement_d18Oc_monthly + 2 * std_measurement_d18Oc_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
axes[2, 1].set_xticks(months_scale)
axes[2, 1].set_xticklabels(month_names, rotation=45, ha="right")
axes[2, 1].set_title('Based on aggregated measurements with 10x higher uncertainty in D47 only')
axes[2, 1].set_xlabel('Month')
axes[2, 1].set_ylabel('d18Oc value (VPDB)')
# axes[2, 1].set_ylim(0.57, 0.63) # Set y-axis limits for better visibility
axes[2, 1].legend()
axes[2, 1].grid(True)
# -- PANEL 7 -- DATA BASED ON INDIVIDUAL MEASUREMENTS WITH HIGHER UNCERTAINTY ON d18Oc ONLY
# Calculate the updated seasonal posterior for D47
std_post_d18Oc_monthlySC_HU_d18O = np.sqrt(np.diag(cov_post_d18Oc_monthlySC_HU_d18O))
# PRIOR
axes[3, 0].plot(months_scale, mu_prior_d18Oc_monthly, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[3, 0].fill_between(
months_scale,
mu_prior_d18Oc_monthly - 2 * std_prior_d18Oc_monthly,
mu_prior_d18Oc_monthly + 2 * std_prior_d18Oc_monthly,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[3, 0].plot(months_scale, mu_likelihood_monthly_d18Oc, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[3, 0].fill_between(
months_scale,
mu_likelihood_monthly_d18Oc - 2 * std_likelihood_monthly_d18Oc,
mu_likelihood_monthly_d18Oc + 2 * std_likelihood_monthly_d18Oc,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[3, 0].plot(months_scale, mu_post_d18Oc_monthlySC_HU_d18O, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[3, 0].fill_between(
months_scale,
mu_post_d18Oc_monthlySC_HU_d18O - 2 * std_post_d18Oc_monthlySC_HU_d18O,
mu_post_d18Oc_monthlySC_HU_d18O + 2 * std_post_d18Oc_monthlySC_HU_d18O,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[3, 0].plot(months_scale, mu_measurement_d18Oc_monthly, label='Measurement Mean (d18Oc)', color='b', marker='x')
axes[3, 0].fill_between(
months_scale,
mu_measurement_d18Oc_monthly - 2 * std_measurement_d18Oc_monthly,
mu_measurement_d18Oc_monthly + 2 * std_measurement_d18Oc_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
axes[3, 0].set_xticks(months_scale)
axes[3, 0].set_xticklabels(month_names, rotation=45, ha="right")
axes[3, 0].set_title('Based on individual measurements with 10x higher uncertainty in d18Oc only')
axes[3, 0].set_xlabel('Month')
axes[3, 0].set_ylabel('d18Oc value (VPDB)')
# axes[3, 0].set_ylim(0.57, 0.63) # Set y-axis limits for better visibility
axes[3, 0].legend()
axes[3, 0].grid(True)
# -- PANEL 8 -- DATA BASED ON AGGREGATED MEASUREMENTS WITH HIGHER UNCERTAINTY ON d18Oc ONLY
# Calculate the updated seasonal posterior for D47
std_post_d18Oc_monthly_aggregated_HU_d18O = np.sqrt(np.diag(cov_post_d18Oc_monthly_aggregated_HU_d18O))
# PRIOR
axes[3, 1].plot(months_scale, mu_prior_d18Oc_monthly, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[3, 1].fill_between(
months_scale,
mu_prior_d18Oc_monthly - 2 * std_prior_d18Oc_monthly,
mu_prior_d18Oc_monthly + 2 * std_prior_d18Oc_monthly,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[3, 1].plot(months_scale, mu_likelihood_monthly_d18Oc, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[3, 1].fill_between(
months_scale,
mu_likelihood_monthly_d18Oc - 2 * std_likelihood_monthly_d18Oc,
mu_likelihood_monthly_d18Oc + 2 * std_likelihood_monthly_d18Oc,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[3, 1].plot(months_scale, mu_post_d18Oc_monthly_aggregated_HU_d18O, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[3, 1].fill_between(
months_scale,
mu_post_d18Oc_monthly_aggregated_HU_d18O - 2 * std_post_d18Oc_monthly_aggregated_HU_d18O,
mu_post_d18Oc_monthly_aggregated_HU_d18O + 2 * std_post_d18Oc_monthly_aggregated_HU_d18O,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[3, 1].plot(months_scale, mu_measurement_d18Oc_monthly, label='Measurement Mean (d18Oc)', color='b', marker='x')
axes[3, 1].fill_between(
months_scale,
mu_measurement_d18Oc_monthly - 2 * std_measurement_d18Oc_monthly,
mu_measurement_d18Oc_monthly + 2 * std_measurement_d18Oc_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
axes[3, 1].set_xticks(months_scale)
axes[3, 1].set_xticklabels(month_names, rotation=45, ha="right")
axes[3, 1].set_title('Based on aggregated measurements with 10x higher uncertainty in d18Oc only')
axes[3, 1].set_xlabel('Month')
axes[3, 1].set_ylabel('d18Oc value (VPDB)')
# axes[3, 1].set_ylim(0.57, 0.63) # Set y-axis limits for better visibility
axes[3, 1].legend()
axes[3, 1].grid(True)
# Show plot
plt.show()
Plot results of uncertainty test with reduced uncertainty on d18Oc assimilationΒΆ
InΒ [49]:
# Plot prior, likelihood and posterior distributions for D47 based on individual measurements
fig, axes = plt.subplots(4, 2, figsize=(20, 25))
fig.suptitle('Sensitivity Test: Effect of reduced D47 and d18Oc Uncertainties on d18Oc', fontsize=24, fontweight='bold') # Provide title for the entire figure
plt.subplots_adjust(top=0.95) # Adjust top spacing to fit the title
plt.subplots_adjust(hspace=0.3) # Adjust vertical spacing between subplots to prevent overlap between titles and x-axis labels
# -- PANEL 1 -- DATA BASED ON INDIVIDUAL MEASUREMENTS
# Calculate the updated seasonal posterior for SST_D47
std_post_d18Oc_monthlySC = np.sqrt(np.diag(cov_post_d18Oc_monthlySC))
std_prior_d18Oc_monthly_original = np.sqrt(np.diag(cov_prior_d18Oc_monthly_original))
# PRIOR
axes[0, 0].plot(months_scale, mu_prior_d18Oc_monthly_original, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[0, 0].fill_between(
months_scale,
mu_prior_d18Oc_monthly_original - 2 * std_prior_d18Oc_monthly_original,
mu_prior_d18Oc_monthly_original + 2 * std_prior_d18Oc_monthly_original,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[0, 0].plot(months_scale, mu_likelihood_monthly_d18Oc, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[0, 0].fill_between(
months_scale,
mu_likelihood_monthly_d18Oc - 2 * std_likelihood_monthly_d18Oc,
mu_likelihood_monthly_d18Oc + 2 * std_likelihood_monthly_d18Oc,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[0, 0].plot(months_scale, mu_post_d18Oc_monthlySC, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[0, 0].fill_between(
months_scale,
mu_post_d18Oc_monthlySC - 2 * std_post_d18Oc_monthlySC,
mu_post_d18Oc_monthlySC + 2 * std_post_d18Oc_monthlySC,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[0, 0].plot(months_scale, mu_measurement_d18Oc_monthly, label='Measurement Mean (SST)', color='b', marker='x')
axes[0, 0].fill_between(
months_scale,
mu_measurement_d18Oc_monthly - 2 * std_measurement_d18Oc_monthly,
mu_measurement_d18Oc_monthly + 2 * std_measurement_d18Oc_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
# Set x-ticks and labels
axes[0, 0].set_xticks(months_scale)
axes[0, 0].set_xticklabels(month_names, rotation=45, ha="right")
axes[0, 0].set_title('Based on individual measurements')
axes[0, 0].set_xlabel('Month')
axes[0, 0].set_ylabel('d18Oc value (VPDB)')
# axes[0, 0].set_ylim(0.54, 0.63) # Set y-axis limits for better visibility
axes[0, 0].legend()
axes[0, 0].grid(True)
# -- PANEL 2 -- DATA BASED ON AGGREGATED MEASUREMENTS
# Calculate the updated seasonal posterior for SST_D47
std_post_d18Oc_monthly_aggregated = np.sqrt(np.diag(cov_post_d18Oc_monthly_aggregated))
# PRIOR
axes[0, 1].plot(months_scale, mu_prior_d18Oc_monthly_original, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[0, 1].fill_between(
months_scale,
mu_prior_d18Oc_monthly_original - 2 * std_prior_d18Oc_monthly_original,
mu_prior_d18Oc_monthly_original + 2 * std_prior_d18Oc_monthly_original,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[0, 1].plot(months_scale, mu_likelihood_monthly_d18Oc, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[0, 1].fill_between(
months_scale,
mu_likelihood_monthly_d18Oc - 2 * std_likelihood_monthly_d18Oc,
mu_likelihood_monthly_d18Oc + 2 * std_likelihood_monthly_d18Oc,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[0, 1].plot(months_scale, mu_post_d18Oc_monthly_aggregated, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[0, 1].fill_between(
months_scale,
mu_post_d18Oc_monthly_aggregated - 2 * std_post_d18Oc_monthly_aggregated,
mu_post_d18Oc_monthly_aggregated + 2 * std_post_d18Oc_monthly_aggregated,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[0, 1].plot(months_scale, mu_measurement_d18Oc_monthly, label='Measurement Mean (SST)', color='b', marker='x')
axes[0, 1].fill_between(
months_scale,
mu_measurement_d18Oc_monthly - 2 * std_measurement_d18Oc_monthly,
mu_measurement_d18Oc_monthly + 2 * std_measurement_d18Oc_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
axes[0, 1].set_xticks(months_scale)
axes[0, 1].set_xticklabels(month_names, rotation=45, ha="right")
axes[0, 1].set_title('Based on aggregated measurements')
axes[0, 1].set_xlabel('Month')
axes[0, 1].set_ylabel('d18Oc value (VPDB)')
# axes[0, 1].set_ylim(0.54, 0.63) # Set y-axis limits for better visibility
axes[0, 1].legend()
axes[0, 1].grid(True)
# -- PANEL 3 -- DATA BASED ON INDIVIDUAL MEASUREMENTS WITH HIGHER UNCERTAINTY ON BOTH D47 AND d18O
# Calculate the updated seasonal posterior for SST_D47
std_post_d18Oc_monthlySC_LU = np.sqrt(np.diag(cov_post_d18Oc_monthlySC_LU))
# PRIOR
axes[1, 0].plot(months_scale, mu_prior_d18Oc_monthly_original, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[1, 0].fill_between(
months_scale,
mu_prior_d18Oc_monthly_original - 2 * std_prior_d18Oc_monthly_original,
mu_prior_d18Oc_monthly_original + 2 * std_prior_d18Oc_monthly_original,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[1, 0].plot(months_scale, mu_likelihood_monthly_d18Oc, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[1, 0].fill_between(
months_scale,
mu_likelihood_monthly_d18Oc - 2 * std_likelihood_monthly_d18Oc,
mu_likelihood_monthly_d18Oc + 2 * std_likelihood_monthly_d18Oc,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[1, 0].plot(months_scale, mu_post_d18Oc_monthlySC_LU, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[1, 0].fill_between(
months_scale,
mu_post_d18Oc_monthlySC_LU - 2 * std_post_d18Oc_monthlySC_LU,
mu_post_d18Oc_monthlySC_LU + 2 * std_post_d18Oc_monthlySC_LU,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[1, 0].plot(months_scale, mu_measurement_d18Oc_monthly, label='Measurement Mean (SST)', color='b', marker='x')
axes[1, 0].fill_between(
months_scale,
mu_measurement_d18Oc_monthly - 2 * std_measurement_d18Oc_monthly,
mu_measurement_d18Oc_monthly + 2 * std_measurement_d18Oc_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
axes[1, 0].set_xticks(months_scale)
axes[1, 0].set_xticklabels(month_names, rotation=45, ha="right")
axes[1, 0].set_title('Based on individual measurements with 10x lower uncertainty in both D47 and d18Oc')
axes[1, 0].set_xlabel('Month')
axes[1, 0].set_ylabel('d18Oc value (VPDB)')
# axes[1, 0].set_ylim(0.54, 0.63) # Set y-axis limits for better visibility
axes[1, 0].legend()
axes[1, 0].grid(True)
# -- PANEL 4 -- DATA BASED ON AGGREGATED MEASUREMENTS WITH HIGHER UNCERTAINTY ON BOTH D47 AND d18O
# Calculate the updated seasonal posterior for SST_D47
std_post_d18Oc_monthly_aggregated_LU = np.sqrt(np.diag(cov_post_d18Oc_monthly_aggregated_LU))
# PRIOR
axes[1, 1].plot(months_scale, mu_prior_d18Oc_monthly_original, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[1, 1].fill_between(
months_scale,
mu_prior_d18Oc_monthly_original - 2 * std_prior_d18Oc_monthly_original,
mu_prior_d18Oc_monthly_original + 2 * std_prior_d18Oc_monthly_original,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[1, 1].plot(months_scale, mu_likelihood_monthly_d18Oc, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[1, 1].fill_between(
months_scale,
mu_likelihood_monthly_d18Oc - 2 * std_likelihood_monthly_d18Oc,
mu_likelihood_monthly_d18Oc + 2 * std_likelihood_monthly_d18Oc,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[1, 1].plot(months_scale, mu_post_d18Oc_monthly_aggregated_LU, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[1, 1].fill_between(
months_scale,
mu_post_d18Oc_monthly_aggregated_LU - 2 * std_post_d18Oc_monthly_aggregated_LU,
mu_post_d18Oc_monthly_aggregated_LU + 2 * std_post_d18Oc_monthly_aggregated_LU,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[1, 1].plot(months_scale, mu_measurement_d18Oc_monthly, label='Measurement Mean (SST)', color='b', marker='x')
axes[1, 1].fill_between(
months_scale,
mu_measurement_d18Oc_monthly - 2 * std_measurement_d18Oc_monthly,
mu_measurement_d18Oc_monthly + 2 * std_measurement_d18Oc_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
axes[1, 1].set_xticks(months_scale)
axes[1, 1].set_xticklabels(month_names, rotation=45, ha="right")
axes[1, 1].set_title('Based on aggregated measurements with 10x lower uncertainty in both D47 and d18Oc')
axes[1, 1].set_xlabel('Month')
axes[1, 1].set_ylabel('d18Oc value (VPDB)')
# axes[1, 1].set_ylim(0.54, 0.63) # Set y-axis limits for better visibility
axes[1, 1].legend()
axes[1, 1].grid(True)
# -- PANEL 5 -- DATA BASED ON INDIVIDUAL MEASUREMENTS WITH HIGHER UNCERTAINTY ON D47 ONLY
# Calculate the updated seasonal posterior for SST_D47
std_post_d18Oc_monthlySC_LU_D47 = np.sqrt(np.diag(cov_post_d18Oc_monthlySC_LU_D47))
# PRIOR
axes[2, 0].plot(months_scale, mu_prior_d18Oc_monthly_original, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[2, 0].fill_between(
months_scale,
mu_prior_d18Oc_monthly_original - 2 * std_prior_d18Oc_monthly_original,
mu_prior_d18Oc_monthly_original + 2 * std_prior_d18Oc_monthly_original,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[2, 0].plot(months_scale, mu_likelihood_monthly_d18Oc, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[2, 0].fill_between(
months_scale,
mu_likelihood_monthly_d18Oc - 2 * std_likelihood_monthly_d18Oc,
mu_likelihood_monthly_d18Oc + 2 * std_likelihood_monthly_d18Oc,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[2, 0].plot(months_scale, mu_post_d18Oc_monthlySC_LU_D47, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[2, 0].fill_between(
months_scale,
mu_post_d18Oc_monthlySC_LU_D47 - 2 * std_post_d18Oc_monthlySC_LU_D47,
mu_post_d18Oc_monthlySC_LU_D47 + 2 * std_post_d18Oc_monthlySC_LU_D47,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[2, 0].plot(months_scale, mu_measurement_d18Oc_monthly, label='Measurement Mean (SST)', color='b', marker='x')
axes[2, 0].fill_between(
months_scale,
mu_measurement_d18Oc_monthly - 2 * std_measurement_d18Oc_monthly,
mu_measurement_d18Oc_monthly + 2 * std_measurement_d18Oc_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
axes[2, 0].set_xticks(months_scale)
axes[2, 0].set_xticklabels(month_names, rotation=45, ha="right")
axes[2, 0].set_title('Based on individual measurements with 10x lower uncertainty in D47 only')
axes[2, 0].set_xlabel('Month')
axes[2, 0].set_ylabel('d18Oc value (VPDB)')
# axes[2, 0].set_ylim(0.54, 0.63) # Set y-axis limits for better visibility
axes[2, 0].legend()
axes[2, 0].grid(True)
# -- PANEL 6 -- DATA BASED ON AGGREGATED MEASUREMENTS WITH HIGHER UNCERTAINTY ON D47 ONLY
# Calculate the updated seasonal posterior for SST_D47
std_post_d18Oc_monthly_aggregated_LU_D47 = np.sqrt(np.diag(cov_post_d18Oc_monthly_aggregated_LU_D47))
# PRIOR
axes[2, 1].plot(months_scale, mu_prior_d18Oc_monthly_original, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[2, 1].fill_between(
months_scale,
mu_prior_d18Oc_monthly_original - 2 * std_prior_d18Oc_monthly_original,
mu_prior_d18Oc_monthly_original + 2 * std_prior_d18Oc_monthly_original,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[2, 1].plot(months_scale, mu_likelihood_monthly_d18Oc, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[2, 1].fill_between(
months_scale,
mu_likelihood_monthly_d18Oc - 2 * std_likelihood_monthly_d18Oc,
mu_likelihood_monthly_d18Oc + 2 * std_likelihood_monthly_d18Oc,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[2, 1].plot(months_scale, mu_post_d18Oc_monthly_aggregated_LU_D47, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[2, 1].fill_between(
months_scale,
mu_post_d18Oc_monthly_aggregated_LU_D47 - 2 * std_post_d18Oc_monthly_aggregated_LU_D47,
mu_post_d18Oc_monthly_aggregated_LU_D47 + 2 * std_post_d18Oc_monthly_aggregated_LU_D47,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[2, 1].plot(months_scale, mu_measurement_d18Oc_monthly, label='Measurement Mean (SST)', color='b', marker='x')
axes[2, 1].fill_between(
months_scale,
mu_measurement_d18Oc_monthly - 2 * std_measurement_d18Oc_monthly,
mu_measurement_d18Oc_monthly + 2 * std_measurement_d18Oc_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
axes[2, 1].set_xticks(months_scale)
axes[2, 1].set_xticklabels(month_names, rotation=45, ha="right")
axes[2, 1].set_title('Based on aggregated measurements with 10x lower uncertainty in D47 only')
axes[2, 1].set_xlabel('Month')
axes[2, 1].set_ylabel('d18Oc value (VPDB)')
# axes[2, 1].set_ylim(0.54, 0.63) # Set y-axis limits for better visibility
axes[2, 1].legend()
axes[2, 1].grid(True)
# -- PANEL 7 -- DATA BASED ON INDIVIDUAL MEASUREMENTS WITH HIGHER UNCERTAINTY ON d18Oc ONLY
# Calculate the updated seasonal posterior for D47
std_post_d18Oc_monthlySC_LU_d18O = np.sqrt(np.diag(cov_post_d18Oc_monthlySC_LU_d18O))
# PRIOR
axes[3, 0].plot(months_scale, mu_prior_d18Oc_monthly, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[3, 0].fill_between(
months_scale,
mu_prior_d18Oc_monthly - 2 * std_prior_d18Oc_monthly,
mu_prior_d18Oc_monthly + 2 * std_prior_d18Oc_monthly,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[3, 0].plot(months_scale, mu_likelihood_monthly_d18Oc, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[3, 0].fill_between(
months_scale,
mu_likelihood_monthly_d18Oc - 2 * std_likelihood_monthly_d18Oc,
mu_likelihood_monthly_d18Oc + 2 * std_likelihood_monthly_d18Oc,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[3, 0].plot(months_scale, mu_post_d18Oc_monthlySC_LU_d18O, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[3, 0].fill_between(
months_scale,
mu_post_d18Oc_monthlySC_LU_d18O - 2 * std_post_d18Oc_monthlySC_LU_d18O,
mu_post_d18Oc_monthlySC_LU_d18O + 2 * std_post_d18Oc_monthlySC_LU_d18O,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[3, 0].plot(months_scale, mu_measurement_d18Oc_monthly, label='Measurement Mean (d18Oc)', color='b', marker='x')
axes[3, 0].fill_between(
months_scale,
mu_measurement_d18Oc_monthly - 2 * std_measurement_d18Oc_monthly,
mu_measurement_d18Oc_monthly + 2 * std_measurement_d18Oc_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
axes[3, 0].set_xticks(months_scale)
axes[3, 0].set_xticklabels(month_names, rotation=45, ha="right")
axes[3, 0].set_title('Based on individual measurements with 10x lower uncertainty in d18Oc only')
axes[3, 0].set_xlabel('Month')
axes[3, 0].set_ylabel('d18Oc value (VPDB)')
# axes[3, 0].set_ylim(0.54, 0.63) # Set y-axis limits for better visibility
axes[3, 0].legend()
axes[3, 0].grid(True)
# -- PANEL 8 -- DATA BASED ON AGGREGATED MEASUREMENTS WITH HIGHER UNCERTAINTY ON d18Oc ONLY
# Calculate the updated seasonal posterior for D47
std_post_d18Oc_monthly_aggregated_LU_d18O = np.sqrt(np.diag(cov_post_d18Oc_monthly_aggregated_LU_d18O))
# PRIOR
axes[3, 1].plot(months_scale, mu_prior_d18Oc_monthly, label='Prior Mean (IPCC_Atlas models)', color='g', marker='o')
axes[3, 1].fill_between(
months_scale,
mu_prior_d18Oc_monthly - 2 * std_prior_d18Oc_monthly,
mu_prior_d18Oc_monthly + 2 * std_prior_d18Oc_monthly,
color='g',
alpha=0.2,
label='+/- 2 SD'
)
# LIKELIHOOD
axes[3, 1].plot(months_scale, mu_likelihood_monthly_d18Oc, label='Likelihood Mean (clumped data)', color='y', marker='o')
axes[3, 1].fill_between(
months_scale,
mu_likelihood_monthly_d18Oc - 2 * std_likelihood_monthly_d18Oc,
mu_likelihood_monthly_d18Oc + 2 * std_likelihood_monthly_d18Oc,
color='y',
alpha=0.2,
label='+/- 2 SD'
)
# POSTERIOR
axes[3, 1].plot(months_scale, mu_post_d18Oc_monthly_aggregated_LU_d18O, label='Posterior Mean (IPCC_Atlas models + clumped data)', color='r', marker='o')
axes[3, 1].fill_between(
months_scale,
mu_post_d18Oc_monthly_aggregated_LU_d18O - 2 * std_post_d18Oc_monthly_aggregated_LU_d18O,
mu_post_d18Oc_monthly_aggregated_LU_d18O + 2 * std_post_d18Oc_monthly_aggregated_LU_d18O,
color='r',
alpha=0.2,
label='+/- 2 SD (Posterior)'
)
# MEASUREMENT DATA
axes[3, 1].plot(months_scale, mu_measurement_d18Oc_monthly, label='Measurement Mean (d18Oc)', color='b', marker='x')
axes[3, 1].fill_between(
months_scale,
mu_measurement_d18Oc_monthly - 2 * std_measurement_d18Oc_monthly,
mu_measurement_d18Oc_monthly + 2 * std_measurement_d18Oc_monthly,
color='b',
alpha=0.2,
label='+/- 2 SD (Measurements)'
)
axes[3, 1].set_xticks(months_scale)
axes[3, 1].set_xticklabels(month_names, rotation=45, ha="right")
axes[3, 1].set_title('Based on aggregated measurements with 10x lower uncertainty in d18Oc only')
axes[3, 1].set_xlabel('Month')
axes[3, 1].set_ylabel('d18Oc value (VPDB)')
# axes[3, 1].set_ylim(0.54, 0.63) # Set y-axis limits for better visibility
axes[3, 1].legend()
axes[3, 1].grid(True)
# Show plot
plt.show()
Aggregate statistics on posterior outcomes on SST-D47 and d18Oc for all casesΒΆ
InΒ [50]:
# Prepare the data for the table
data = {
"Data Type": [
"Prior estimates",
"In situ measurements",
"Individual Measurements",
"Individual Measurements (SD D47 * 10)",
"Individual Measurements (SD d18Oc * 10)",
"Individual Measurements (both SDs * 10)",
"Individual Measurements (SD D47 * 0.1)",
"Individual Measurements (SD d18Oc * 0.1)",
"Individual Measurements (both SDs * 0.1)",
"Aggregated Measurements",
"Aggregated Measurements (SD D47 * 10)",
"Aggregated Measurements (SD d18Oc * 10)",
"Aggregated Measurements (both SDs * 10)",
"Aggregated Measurements (SD D47 * 0.1)",
"Aggregated Measurements (SD d18Oc * 0.1)",
"Aggregated Measurements (both SDs * 0.1)"
]
}
# Add monthly means and standard deviations to the data dictionary
months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
# Process and rename the means and standard deviations for each data source
D47_means_prior = mu_prior_SST_D47_monthly_original
D47_stds_prior = np.sqrt(np.diag(cov_prior_SST_D47_monthly_original))
D47_means_individual = mu_post_SST_D47_monthlySC
D47_stds_individual = np.sqrt(np.diag(cov_post_SST_D47_monthlySC))
D47_means_individual_hu_d47 = mu_post_SST_D47_monthlySC_HU_D47
D47_stds_individual_hu_d47 = np.sqrt(np.diag(cov_post_SST_D47_monthlySC_HU_D47))
D47_means_individual_hu_d18o = mu_post_SST_D47_monthlySC_HU_d18O
D47_stds_individual_hu_d18o = np.sqrt(np.diag(cov_post_SST_D47_monthlySC_HU_d18O))
D47_means_individual_hu = mu_post_SST_D47_monthlySC_HU
D47_stds_individual_hu = np.sqrt(np.diag(cov_post_SST_D47_monthlySC_HU))
D47_means_individual_lu_d47 = mu_post_SST_D47_monthlySC_LU_D47
D47_stds_individual_lu_d47 = np.sqrt(np.diag(cov_post_SST_D47_monthlySC_LU_D47))
D47_means_individual_lu_d18o = mu_post_SST_D47_monthlySC_LU_d18O
D47_stds_individual_lu_d18o = np.sqrt(np.diag(cov_post_SST_D47_monthlySC_LU_d18O))
D47_means_individual_lu = mu_post_SST_D47_monthlySC_LU
D47_stds_individual_lu = np.sqrt(np.diag(cov_post_SST_D47_monthlySC_LU))
D47_means_aggregated = mu_post_SST_D47_monthly_aggregated
D47_stds_aggregated = np.sqrt(np.diag(cov_post_SST_D47_monthly_aggregated))
D47_means_aggregated_hu_d47 = mu_post_SST_D47_monthly_aggregated_HU_D47
D47_stds_aggregated_hu_d47 = np.sqrt(np.diag(cov_post_SST_D47_monthly_aggregated_HU_D47))
D47_means_aggregated_hu_d18o = mu_post_SST_D47_monthly_aggregated_HU_d18O
D47_stds_aggregated_hu_d18o = np.sqrt(np.diag(cov_post_SST_D47_monthly_aggregated_HU_d18O))
D47_means_aggregated_hu = mu_post_SST_D47_monthly_aggregated_HU
D47_stds_aggregated_hu = np.sqrt(np.diag(cov_post_SST_D47_monthly_aggregated_HU))
D47_means_aggregated_lu_d47 = mu_post_SST_D47_monthly_aggregated_LU_D47
D47_stds_aggregated_lu_d47 = np.sqrt(np.diag(cov_post_SST_D47_monthly_aggregated_LU_D47))
D47_means_aggregated_lu_d18o = mu_post_SST_D47_monthly_aggregated_LU_d18O
D47_stds_aggregated_lu_d18o = np.sqrt(np.diag(cov_post_SST_D47_monthly_aggregated_LU_d18O))
D47_means_aggregated_lu = mu_post_SST_D47_monthly_aggregated_LU
D47_stds_aggregated_lu = np.sqrt(np.diag(cov_post_SST_D47_monthly_aggregated_LU))
d18Oc_means_prior = mu_prior_d18Oc_monthly_original
d18Oc_stds_prior = np.sqrt(np.diag(cov_prior_d18Oc_monthly_original))
d18Oc_means_individual = mu_post_d18Oc_monthlySC
d18Oc_stds_individual = np.sqrt(np.diag(cov_post_d18Oc_monthlySC))
d18Oc_means_individual_hu_d47 = mu_post_d18Oc_monthlySC_HU_D47
d18Oc_stds_individual_hu_d47 = np.sqrt(np.diag(cov_post_d18Oc_monthlySC_HU_D47))
d18Oc_means_individual_hu_d18o = mu_post_d18Oc_monthlySC_HU_d18O
d18Oc_stds_individual_hu_d18o = np.sqrt(np.diag(cov_post_d18Oc_monthlySC_HU_d18O))
d18Oc_means_individual_hu = mu_post_d18Oc_monthlySC_HU
d18Oc_stds_individual_hu = np.sqrt(np.diag(cov_post_d18Oc_monthlySC_HU))
d18Oc_means_individual_lu_d47 = mu_post_d18Oc_monthlySC_LU_D47
d18Oc_stds_individual_lu_d47 = np.sqrt(np.diag(cov_post_d18Oc_monthlySC_LU_D47))
d18Oc_means_individual_lu_d18o = mu_post_d18Oc_monthlySC_LU_d18O
d18Oc_stds_individual_lu_d18o = np.sqrt(np.diag(cov_post_d18Oc_monthlySC_LU_d18O))
d18Oc_means_individual_lu = mu_post_d18Oc_monthlySC_LU
d18Oc_stds_individual_lu = np.sqrt(np.diag(cov_post_d18Oc_monthlySC_LU))
d18Oc_means_aggregated = mu_post_d18Oc_monthly_aggregated
d18Oc_stds_aggregated = np.sqrt(np.diag(cov_post_d18Oc_monthly_aggregated))
d18Oc_means_aggregated_hu_d47 = mu_post_d18Oc_monthly_aggregated_HU_D47
d18Oc_stds_aggregated_hu_d47 = np.sqrt(np.diag(cov_post_d18Oc_monthly_aggregated_HU_D47))
d18Oc_means_aggregated_hu_d18o = mu_post_d18Oc_monthly_aggregated_HU_d18O
d18Oc_stds_aggregated_hu_d18o = np.sqrt(np.diag(cov_post_d18Oc_monthly_aggregated_HU_d18O))
d18Oc_means_aggregated_hu = mu_post_d18Oc_monthly_aggregated_HU
d18Oc_stds_aggregated_hu = np.sqrt(np.diag(cov_post_d18Oc_monthly_aggregated_HU))
d18Oc_means_aggregated_lu_d47 = mu_post_d18Oc_monthly_aggregated_LU_D47
d18Oc_stds_aggregated_lu_d47 = np.sqrt(np.diag(cov_post_d18Oc_monthly_aggregated_LU_D47))
d18Oc_means_aggregated_lu_d18o = mu_post_d18Oc_monthly_aggregated_LU_d18O
d18Oc_stds_aggregated_lu_d18o = np.sqrt(np.diag(cov_post_d18Oc_monthly_aggregated_LU_d18O))
d18Oc_means_aggregated_lu = mu_post_d18Oc_monthly_aggregated_LU
d18Oc_stds_aggregated_lu = np.sqrt(np.diag(cov_post_d18Oc_monthly_aggregated_LU))
# Combine all means and standard deviations into the data structure
all_D47_means = [
D47_means_prior,
mu_measurement_SST_D47_monthly,
D47_means_individual,
D47_means_individual_hu_d47,
D47_means_individual_hu_d18o,
D47_means_individual_hu,
D47_means_individual_lu_d47,
D47_means_individual_lu_d18o,
D47_means_individual_lu,
D47_means_aggregated,
D47_means_aggregated_hu_d47,
D47_means_aggregated_hu_d18o,
D47_means_aggregated_hu,
D47_means_aggregated_lu_d47,
D47_means_aggregated_lu_d18o,
D47_means_aggregated_lu
]
all_D47_stds = [
D47_stds_prior,
std_measurement_SST_D47_monthly,
D47_stds_individual,
D47_stds_individual_hu_d47,
D47_stds_individual_hu_d18o,
D47_stds_individual_hu,
D47_stds_individual_lu_d47,
D47_stds_individual_lu_d18o,
D47_stds_individual_lu,
D47_stds_aggregated,
D47_stds_aggregated_hu_d47,
D47_stds_aggregated_hu_d18o,
D47_stds_aggregated_hu,
D47_stds_aggregated_lu_d47,
D47_stds_aggregated_lu_d18o,
D47_stds_aggregated_lu
]
all_d18Oc_means = [
d18Oc_means_prior,
mu_measurement_d18Oc_monthly,
d18Oc_means_individual,
d18Oc_means_individual_hu_d47,
d18Oc_means_individual_hu_d18o,
d18Oc_means_individual_hu,
d18Oc_means_individual_lu_d47,
d18Oc_means_individual_lu_d18o,
d18Oc_means_individual_lu,
d18Oc_means_aggregated,
d18Oc_means_aggregated_hu_d47,
d18Oc_means_aggregated_hu_d18o,
d18Oc_means_aggregated_hu,
d18Oc_means_aggregated_lu_d47,
d18Oc_means_aggregated_lu_d18o,
d18Oc_means_aggregated_lu
]
all_d18Oc_stds = [
d18Oc_stds_prior,
std_measurement_d18Oc_monthly,
d18Oc_stds_individual,
d18Oc_stds_individual_hu_d47,
d18Oc_stds_individual_hu_d18o,
d18Oc_stds_individual_hu,
d18Oc_stds_individual_lu_d47,
d18Oc_stds_individual_lu_d18o,
d18Oc_stds_individual_lu,
d18Oc_stds_aggregated,
d18Oc_stds_aggregated_hu_d47,
d18Oc_stds_aggregated_hu_d18o,
d18Oc_stds_aggregated_hu,
d18Oc_stds_aggregated_lu_d47,
d18Oc_stds_aggregated_lu_d18o,
d18Oc_stds_aggregated_lu
]
# Create a dictionary to store all the data
all_data = {"Data Type": data["Data Type"]}
# Add D47 means and stds
for i, month in enumerate(months):
all_data[f"{month} D47 Mean"] = [means[i] for means in all_D47_means]
all_data[f"{month} D47 Std"] = [stds[i] for stds in all_D47_stds]
# Add d18Oc means and stds
for i, month in enumerate(months):
all_data[f"{month} d18Oc Mean"] = [means[i] for means in all_d18Oc_means]
all_data[f"{month} d18Oc Std"] = [stds[i] for stds in all_d18Oc_stds]
# Create the Pandas DataFrame
all_uncertainty_test_table_df = pd.DataFrame(all_data)
# Set the 'Data Type' column as the index
all_uncertainty_test_table_df = all_uncertainty_test_table_df.set_index("Data Type")
# Export the table to a CSV file
all_uncertainty_test_table_df.to_csv("Sensitivity_test_D47_d18Oc_uncertainties/all_uncertainty_test_table.csv")
print("Combined data exported to Sensitivity_test_D47_d18Oc_uncertainties/all_uncertainty_test_table.csv")
Combined data exported to Sensitivity_test_D47_d18Oc_uncertainties/all_uncertainty_test_table.csv
Plot accuracy and precision in function of uncertaintyΒΆ
Extract data on accuracy and precisionΒΆ
InΒ [51]:
# Calculate accuracy of monthly D47 and d18Oc from difference between mean and measurement mean
accuracy_D47 = all_uncertainty_test_table_df.filter(like='D47 Mean').subtract(mu_measurement_SST_D47_monthly).abs()
accuracy_d18Oc = all_uncertainty_test_table_df.filter(like='d18Oc Mean').subtract(mu_measurement_d18Oc_monthly).abs()
# Calculate precision of monthly D47 and d18Oc from standard deviation
precision_D47 = all_uncertainty_test_table_df.filter(like='D47 Std')
precision_d18Oc = all_uncertainty_test_table_df.filter(like='d18Oc Std')
Combine plots of accuracy and precisionΒΆ
InΒ [52]:
fig, ((ax2, ax4), (ax1, ax3)) = plt.subplots(
2, 2, figsize=(18, 12), sharex='col'
)
fig.subplots_adjust(hspace=0.1, wspace=0.25) # reduce vertical & horizontal spacing
# Define a colormap across all months
cmap = plt.cm.coolwarm
colors = [cmap(i / (len(months)-1)) for i in range(len(months))]
color_dict = dict(zip(months, colors))
# ---------------------------------------------------------
# --- Accuracy Plot (Bottom Left: D47) ---
for month in months:
ax1.scatter(accuracy_D47.index, accuracy_D47[f"{month} D47 Mean"], s=20, label=month, color=color_dict[month])
ax1.set_xticks(accuracy_D47.index)
ax1.set_xticklabels(accuracy_D47.index, rotation=45, ha="right")
ax1.set_xlabel('Data Type')
ax1.set_ylabel('D47 Accuracy (β° I-CDES)\n(Abs. Difference from Measurement Mean)', color='blue')
ax1.tick_params(axis='y', labelcolor='blue')
# Secondary axis for temperature
ax1_temp = ax1.twinx()
ax1_temp.set_ylabel('Temperature Accuracy (Β°C)', color='red')
ax1_temp.tick_params(axis='y', labelcolor='red')
temp_min1 = 0
temp_max1 = np.max(mu_measurement_SST_monthly) - D47c.OGLS23.T47(
D47=D47c.OGLS23.T47(T=np.mean(mu_measurement_SST_monthly))[0] + 0.05
)[0]
ax1_temp.set_ylim(temp_min1, temp_max1)
ax1.set_title('D47 Accuracy by Data Type and Month')
# ---------------------------------------------------------
# --- Precision Plot (Top Left: D47) ---
for month in months:
ax2.scatter(precision_D47.index, precision_D47[f"{month} D47 Std"], s=20, color=color_dict[month])
ax2.set_ylabel('D47 Precision (β° I-CDES)\nUncertainty on Estimate (1 SD)', color='blue')
ax2.tick_params(axis='y', labelcolor='blue')
ax2_temp = ax2.twinx()
ax2_temp.set_ylabel('Temperature Precision (Β°C)', color='red')
ax2_temp.tick_params(axis='y', labelcolor='red')
temp_min2 = 0
temp_max2 = np.max(mu_measurement_SST_monthly) - D47c.OGLS23.T47(
D47=D47c.OGLS23.T47(T=np.mean(mu_measurement_SST_monthly))[0] + 0.006
)[0]
ax2_temp.set_ylim(temp_min2, temp_max2)
ax2.set_title('D47 Precision by Data Type and Month')
# ---------------------------------------------------------
# --- Accuracy Plot (Bottom Right: Ξ΄18Oc) ---
for month in months:
ax3.scatter(accuracy_d18Oc.index, accuracy_d18Oc[f"{month} d18Oc Mean"], s=20, label=month, color=color_dict[month])
ax3.set_xticks(accuracy_d18Oc.index)
ax3.set_xticklabels(accuracy_d18Oc.index, rotation=45, ha="right")
ax3.set_xlabel('Data Type')
ax3.set_ylabel('Ξ΄18Oc Accuracy (β° VPDB)\n(Abs. Difference from Measurement Mean)', color='blue')
ax3.tick_params(axis='y', labelcolor='blue')
ax3.set_title('Ξ΄18Oc Accuracy by Data Type and Month')
# Secondary axis for temperature, based on d18Oc-temperature conversion used above
ax3_temp = ax3.twinx()
ax3_temp.set_ylabel('Temperature Accuracy (Β°C)', color='red')
ax3_temp.tick_params(axis='y', labelcolor='red')
temp_min3 = 0
temp_max3 = np.max(mu_measurement_SST_monthly) - (20.6 - 4.34 * (((20.6 - np.max(mu_measurement_SST_monthly)) / 4.34 - 0.27) + 1.4 + 0.27))
ax3_temp.set_ylim(temp_min3, temp_max3)
# ---------------------------------------------------------
# --- Precision Plot (Top Right: Ξ΄18Oc) ---
for month in months:
ax4.scatter(precision_d18Oc.index, precision_d18Oc[f"{month} d18Oc Std"], s=20, color=color_dict[month])
ax4.set_ylabel('Ξ΄18Oc Precision (β° VPDB)\nUncertainty on Estimate (1 SD)', color='blue')
ax4.tick_params(axis='y', labelcolor='blue')
ax4.set_title('Ξ΄18Oc Precision by Data Type and Month')
# Secondary axis for temperature, based on d18Oc-temperature conversion used above
ax4_temp = ax4.twinx()
ax4_temp.set_ylabel('Temperature Precision (Β°C)', color='red')
ax4_temp.tick_params(axis='y', labelcolor='red')
temp_min4 = 0
temp_max4 = np.max(mu_measurement_SST_monthly) - (20.6 - 4.34 * (((20.6 - np.max(mu_measurement_SST_monthly)) / 4.34 - 0.27) + 0.7 + 0.27))
ax4_temp.set_ylim(temp_min4, temp_max4)
# ---------------------------------------------------------
# Shared legend above all plots
handles1, labels1 = ax1.get_legend_handles_labels()
fig.legend(handles1, labels1,
loc="upper center",
bbox_to_anchor=(0.5, 1.01),
ncol=len(months),
frameon=False)
# Hide top x-axis labels
ax2.tick_params(axis='x', labelbottom=False)
ax4.tick_params(axis='x', labelbottom=False)
plt.tight_layout(rect=[0, 0, 1, 0.95]) # leave space for legend
plt.show()
Combined plot of measurements, priors and posteriors with precision uncertaintiesΒΆ
InΒ [53]:
fig, (ax1, ax3) = plt.subplots(
2, 1, figsize=(8, 10), sharex='col'
)
fig.subplots_adjust(hspace=0.1, wspace=0.25) # reduce vertical & horizontal spacing
# Define a colormap across all months
cmap = plt.cm.coolwarm
colors = [cmap(i / (len(months)-1)) for i in range(len(months))]
color_dict = dict(zip(months, colors))
# Define offsets for x-axis to avoid overlapping error bars
x_offsets = np.linspace(-0.3, 0.3, len(months))
# Define x values for each data type
x_values = np.arange(len(all_uncertainty_test_table_df.index))
# --- D47 plot ---
for month in months:
ax1.errorbar(
x = x_values + x_offsets[months.index(month)],
y = all_uncertainty_test_table_df[f"{month} D47 Mean"],
yerr = all_uncertainty_test_table_df[f"{month} D47 Std"],
capsize = 3,
fmt='.',
label=month,
color=color_dict[month]
)
# Add light green rectangle to highlight prior
ax1.add_patch(
Rectangle(
(-0.5, ax1.get_ylim()[0]), # (x,y)
1, # width
ax1.get_ylim()[1] - ax1.get_ylim()[0], # height
color='lightgreen',
alpha=0.3,
)
)
# Add light blue rectangle to highlight measurement
ax1.add_patch(
Rectangle(
(0.5, ax1.get_ylim()[0]), # (x,y)
1, # width
ax1.get_ylim()[1] - ax1.get_ylim()[0], # height
color='lightblue',
alpha=0.3,
)
)
# Add light red rectangle to highlight posteriors
ax1.add_patch(
Rectangle(
(1.5, ax1.get_ylim()[0]), # (x,y)
14, # width
ax1.get_ylim()[1] - ax1.get_ylim()[0], # height
color='red',
alpha=0.1,
)
)
ax1.set_xticks(x_values)
ax1.set_xticklabels(all_uncertainty_test_table_df.index, rotation=45, ha="right")
# ax1.set_xlabel('test case')
ax1.set_ylabel('D47 value (β° I-CDES)', color='purple')
ax1.tick_params(axis='y', labelcolor='purple')
# Secondary axis for temperature
ax1_temp = ax1.twinx()
ax1_temp.set_ylabel('D47 Temperature (Β°C)', color='purple')
ax1_temp.tick_params(axis='y', labelcolor='purple')
ax1_temp.set_yticks(
ticks = D47c.OGLS23.T47(T = np.linspace(0, 50, 25))[0],
labels = np.linspace(0, 50, 25).astype(int)
)
ax1_temp.set_ylim(ax1.get_ylim())
# --- Ξ΄18Oc Plot ---
for month in months:
ax3.errorbar(
x = x_values + x_offsets[months.index(month)],
y = all_uncertainty_test_table_df[f"{month} d18Oc Mean"],
yerr = all_uncertainty_test_table_df[f"{month} d18Oc Std"],
capsize = 3,
fmt='.',
label=month,
color=color_dict[month]
)
# Add light green rectangle to highlight prior
ax3.add_patch(
Rectangle(
(-0.5, ax3.get_ylim()[0]), # (x,y)
1, # width
ax3.get_ylim()[1] - ax3.get_ylim()[0], # height
color='lightgreen',
alpha=0.3,
)
)
# Add light blue rectangle to highlight measurement
ax3.add_patch(
Rectangle(
(0.5, ax3.get_ylim()[0]), # (x,y)
1, # width
ax3.get_ylim()[1] - ax3.get_ylim()[0], # height
color='lightblue',
alpha=0.3,
)
)
# Add light red rectangle to highlight posteriors
ax3.add_patch(
Rectangle(
(1.5, ax3.get_ylim()[0]), # (x,y)
14, # width
ax3.get_ylim()[1] - ax3.get_ylim()[0], # height
color='red',
alpha=0.1,
)
)
ax3.set_xticks(x_values)
ax3.set_xticklabels(all_uncertainty_test_table_df.index, rotation=45, ha="right")
ax3.set_ylabel('Ξ΄18Oc value (β° VPDB)', color='darkblue')
ax3.tick_params(axis='y', labelcolor='darkblue')
# Secondary axis for temperature, based on d18Oc-temperature conversion used above
ax3_temp = ax3.twinx()
ax3_temp.set_ylabel('Ξ΄18Oc Temperature (Β°C; assuming Ξ΄18Ow of 0β°)', color='darkblue')
ax3_temp.tick_params(axis='y', labelcolor='darkblue')
ax3_temp.set_yticks(
ticks = ((20.6 - np.linspace(0, 50, 25)) / 4.34 - 0.27) + 0 + 0.27,
labels = np.linspace(0, 50, 25).astype(int)
)
ax3_temp.set_ylim(ax3.get_ylim())
ax3.set_title('Ξ΄18Oc value by test case and Month')
# ---------------------------------------------------------
# Shared legend above all plots
handles1, labels1 = ax1.get_legend_handles_labels()
fig.legend(handles1, labels1,
loc="upper center",
bbox_to_anchor=(0.5, 1.01),
ncol=len(months) / 2,
frameon=False)
plt.tight_layout(rect=[0, 0, 1, 0.95]) # leave space for legend
plt.show()
Aggregate statistics on posterior outcomes on SST and SAT for all casesΒΆ
InΒ [54]:
# Prepare the data for the table
data = {
"Data Type": [
"Prior estimates",
"In situ measurements",
"Individual Measurements",
"Individual Measurements (SD D47 * 10)",
"Individual Measurements (SD d18Oc * 10)",
"Individual Measurements (both SDs * 10)",
"Individual Measurements (SD D47 * 0.1)",
"Individual Measurements (SD d18Oc * 0.1)",
"Individual Measurements (both SDs * 0.1)",
"Aggregated Measurements",
"Aggregated Measurements (SD D47 * 10)",
"Aggregated Measurements (SD d18Oc * 10)",
"Aggregated Measurements (both SDs * 10)",
"Aggregated Measurements (SD D47 * 0.1)",
"Aggregated Measurements (SD d18Oc * 0.1)",
"Aggregated Measurements (both SDs * 0.1)"
]
}
# Add monthly means and standard deviations to the data dictionary
months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
# Process and rename the means and standard deviations for each data source
SST_means_prior = D47c.OGLS23.T47(D47 = mu_prior_SST_D47_monthly_original, sD47 = cov_prior_SST_D47_monthly_original, return_covar = True)[0]
SST_stds_prior = np.sqrt(np.diag(D47c.OGLS23.T47(D47 = mu_prior_SST_D47_monthly_original, sD47 = cov_prior_SST_D47_monthly_original, return_covar = True)[1]))
SST_means_individual = D47c.OGLS23.T47(D47 = mu_post_SST_D47_monthlySC, sD47 = cov_post_SST_D47_monthlySC, return_covar = True)[0]
SST_stds_individual = np.sqrt(np.diag(D47c.OGLS23.T47(D47 = mu_post_SST_D47_monthlySC, sD47 = cov_post_SST_D47_monthlySC, return_covar = True)[1]))
SST_means_individual_hu_d47 = D47c.OGLS23.T47(D47 = mu_post_SST_D47_monthlySC_HU_D47, sD47 = cov_post_SST_D47_monthlySC_HU_D47, return_covar = True)[0]
SST_stds_individual_hu_d47 = np.sqrt(np.diag(D47c.OGLS23.T47(D47 = mu_post_SST_D47_monthlySC_HU_D47, sD47 = cov_post_SST_D47_monthlySC_HU_D47, return_covar = True)[1]))
SST_means_individual_hu_d18o = D47c.OGLS23.T47(D47 = mu_post_SST_D47_monthlySC_HU_d18O, sD47 = cov_post_SST_D47_monthlySC_HU_d18O, return_covar = True)[0]
SST_stds_individual_hu_d18o = np.sqrt(np.diag(D47c.OGLS23.T47(D47 = mu_post_SST_D47_monthlySC_HU_d18O, sD47 = cov_post_SST_D47_monthlySC_HU_d18O, return_covar = True)[1]))
SST_means_individual_hu = D47c.OGLS23.T47(D47 = mu_post_SST_D47_monthlySC_HU, sD47 = cov_post_SST_D47_monthlySC_HU, return_covar = True)[0]
SST_stds_individual_hu = np.sqrt(np.diag(D47c.OGLS23.T47(D47 = mu_post_SST_D47_monthlySC_HU, sD47 = cov_post_SST_D47_monthlySC_HU, return_covar = True)[1]))
SST_means_individual_lu_d47 = D47c.OGLS23.T47(D47 = mu_post_SST_D47_monthlySC_LU_D47, sD47 = cov_post_SST_D47_monthlySC_LU_D47, return_covar = True)[0]
SST_stds_individual_lu_d47 = np.sqrt(np.diag(D47c.OGLS23.T47(D47 = mu_post_SST_D47_monthlySC_LU_D47, sD47 = cov_post_SST_D47_monthlySC_LU_D47, return_covar = True)[1]))
SST_means_individual_lu_d18o = D47c.OGLS23.T47(D47 = mu_post_SST_D47_monthlySC_LU_d18O, sD47 = cov_post_SST_D47_monthlySC_LU_d18O, return_covar = True)[0]
SST_stds_individual_lu_d18o = np.sqrt(np.diag(D47c.OGLS23.T47(D47 = mu_post_SST_D47_monthlySC_LU_d18O, sD47 = cov_post_SST_D47_monthlySC_LU_d18O, return_covar = True)[1]))
SST_means_individual_lu = D47c.OGLS23.T47(D47 = mu_post_SST_D47_monthlySC_LU, sD47 = cov_post_SST_D47_monthlySC_LU, return_covar = True)[0]
SST_stds_individual_lu = np.sqrt(np.diag(D47c.OGLS23.T47(D47 = mu_post_SST_D47_monthlySC_LU, sD47 = cov_post_SST_D47_monthlySC_LU, return_covar = True)[1]))
SST_means_aggregated = D47c.OGLS23.T47(D47 = mu_post_SST_D47_monthly_aggregated, sD47 = cov_post_SST_D47_monthly_aggregated, return_covar = True)[0]
SST_stds_aggregated = np.sqrt(np.diag(D47c.OGLS23.T47(D47 = mu_post_SST_D47_monthly_aggregated, sD47 = cov_post_SST_D47_monthly_aggregated, return_covar = True)[1]))
SST_means_aggregated_hu_d47 = D47c.OGLS23.T47(D47 = mu_post_SST_D47_monthly_aggregated_HU_D47, sD47 = cov_post_SST_D47_monthly_aggregated_HU_D47, return_covar = True)[0]
SST_stds_aggregated_hu_d47 = np.sqrt(np.diag(D47c.OGLS23.T47(D47 = mu_post_SST_D47_monthly_aggregated_HU_D47, sD47 = cov_post_SST_D47_monthly_aggregated_HU_D47, return_covar = True)[1]))
SST_means_aggregated_hu_d18o = D47c.OGLS23.T47(D47 = mu_post_SST_D47_monthly_aggregated_HU_d18O, sD47 = cov_post_SST_D47_monthly_aggregated_HU_d18O, return_covar = True)[0]
SST_stds_aggregated_hu_d18o = np.sqrt(np.diag(D47c.OGLS23.T47(D47 = mu_post_SST_D47_monthly_aggregated_HU_d18O, sD47 = cov_post_SST_D47_monthly_aggregated_HU_d18O, return_covar = True)[1]))
SST_means_aggregated_hu = D47c.OGLS23.T47(D47 = mu_post_SST_D47_monthly_aggregated_HU, sD47 = cov_post_SST_D47_monthly_aggregated_HU, return_covar = True)[0]
SST_stds_aggregated_hu = np.sqrt(np.diag(D47c.OGLS23.T47(D47 = mu_post_SST_D47_monthly_aggregated_HU, sD47 = cov_post_SST_D47_monthly_aggregated_HU, return_covar = True)[1]))
SST_means_aggregated_lu_d47 = D47c.OGLS23.T47(D47 = mu_post_SST_D47_monthly_aggregated_LU_D47, sD47 = cov_post_SST_D47_monthly_aggregated_LU_D47, return_covar = True)[0]
SST_stds_aggregated_lu_d47 = np.sqrt(np.diag(D47c.OGLS23.T47(D47 = mu_post_SST_D47_monthly_aggregated_LU_D47, sD47 = cov_post_SST_D47_monthly_aggregated_LU_D47, return_covar = True)[1]))
SST_means_aggregated_lu_d18o = D47c.OGLS23.T47(D47 = mu_post_SST_D47_monthly_aggregated_LU_d18O, sD47 = cov_post_SST_D47_monthly_aggregated_LU_d18O, return_covar = True)[0]
SST_stds_aggregated_lu_d18o = np.sqrt(np.diag(D47c.OGLS23.T47(D47 = mu_post_SST_D47_monthly_aggregated_LU_d18O, sD47 = cov_post_SST_D47_monthly_aggregated_LU_d18O, return_covar = True)[1]))
SST_means_aggregated_lu = D47c.OGLS23.T47(D47 = mu_post_SST_D47_monthly_aggregated_LU, sD47 = cov_post_SST_D47_monthly_aggregated_LU, return_covar = True)[0]
SST_stds_aggregated_lu = np.sqrt(np.diag(D47c.OGLS23.T47(D47 = mu_post_SST_D47_monthly_aggregated_LU, sD47 = cov_post_SST_D47_monthly_aggregated_LU, return_covar = True)[1]))
SAT_means_prior = D47c.OGLS23.T47(D47 = mu_prior_SAT_D47_monthly_original, sD47 = cov_prior_SAT_D47_monthly_original, return_covar = True)[0]
SAT_stds_prior = np.sqrt(np.diag(D47c.OGLS23.T47(D47 = mu_prior_SAT_D47_monthly_original, sD47 = cov_prior_SAT_D47_monthly_original, return_covar = True)[1]))
SAT_means_individual = D47c.OGLS23.T47(D47 = mu_post_SAT_D47_monthlySC, sD47 = cov_post_SAT_D47_monthlySC, return_covar = True)[0]
SAT_stds_individual = np.sqrt(np.diag(D47c.OGLS23.T47(D47 = mu_post_SAT_D47_monthlySC, sD47 = cov_post_SAT_D47_monthlySC, return_covar = True)[1]))
SAT_means_individual_hu_d47 = D47c.OGLS23.T47(D47 = mu_post_SAT_D47_monthlySC_HU_D47, sD47 = cov_post_SAT_D47_monthlySC_HU_D47, return_covar = True)[0]
SAT_stds_individual_hu_d47 = np.sqrt(np.diag(D47c.OGLS23.T47(D47 = mu_post_SAT_D47_monthlySC_HU_D47, sD47 = cov_post_SAT_D47_monthlySC_HU_D47, return_covar = True)[1]))
SAT_means_individual_hu_d18o = D47c.OGLS23.T47(D47 = mu_post_SAT_D47_monthlySC_HU_d18O, sD47 = cov_post_SAT_D47_monthlySC_HU_d18O, return_covar = True)[0]
SAT_stds_individual_hu_d18o = np.sqrt(np.diag(D47c.OGLS23.T47(D47 = mu_post_SAT_D47_monthlySC_HU_d18O, sD47 = cov_post_SAT_D47_monthlySC_HU_d18O, return_covar = True)[1]))
SAT_means_individual_hu = D47c.OGLS23.T47(D47 = mu_post_SAT_D47_monthlySC_HU, sD47 = cov_post_SAT_D47_monthlySC_HU, return_covar = True)[0]
SAT_stds_individual_hu = np.sqrt(np.diag(D47c.OGLS23.T47(D47 = mu_post_SAT_D47_monthlySC_HU, sD47 = cov_post_SAT_D47_monthlySC_HU, return_covar = True)[1]))
SAT_means_individual_lu_d47 = D47c.OGLS23.T47(D47 = mu_post_SAT_D47_monthlySC_LU_D47, sD47 = cov_post_SAT_D47_monthlySC_LU_D47, return_covar = True)[0]
SAT_stds_individual_lu_d47 = np.sqrt(np.diag(D47c.OGLS23.T47(D47 = mu_post_SAT_D47_monthlySC_LU_D47, sD47 = cov_post_SAT_D47_monthlySC_LU_D47, return_covar = True)[1]))
SAT_means_individual_lu_d18o = D47c.OGLS23.T47(D47 = mu_post_SAT_D47_monthlySC_LU_d18O, sD47 = cov_post_SAT_D47_monthlySC_LU_d18O, return_covar = True)[0]
SAT_stds_individual_lu_d18o = np.sqrt(np.diag(D47c.OGLS23.T47(D47 = mu_post_SAT_D47_monthlySC_LU_d18O, sD47 = cov_post_SAT_D47_monthlySC_LU_d18O, return_covar = True)[1]))
SAT_means_individual_lu = D47c.OGLS23.T47(D47 = mu_post_SAT_D47_monthlySC_LU, sD47 = cov_post_SAT_D47_monthlySC_LU, return_covar = True)[0]
SAT_stds_individual_lu = np.sqrt(np.diag(D47c.OGLS23.T47(D47 = mu_post_SAT_D47_monthlySC_LU, sD47 = cov_post_SAT_D47_monthlySC_LU, return_covar = True)[1]))
SAT_means_aggregated = D47c.OGLS23.T47(D47 = mu_post_SAT_D47_monthly_aggregated, sD47 = cov_post_SAT_D47_monthly_aggregated, return_covar = True)[0]
SAT_stds_aggregated = np.sqrt(np.diag(D47c.OGLS23.T47(D47 = mu_post_SAT_D47_monthly_aggregated, sD47 = cov_post_SAT_D47_monthly_aggregated, return_covar = True)[1]))
SAT_means_aggregated_hu_d47 = D47c.OGLS23.T47(D47 = mu_post_SAT_D47_monthly_aggregated_HU_D47, sD47 = cov_post_SAT_D47_monthly_aggregated_HU_D47, return_covar = True)[0]
SAT_stds_aggregated_hu_d47 = np.sqrt(np.diag(D47c.OGLS23.T47(D47 = mu_post_SAT_D47_monthly_aggregated_HU_D47, sD47 = cov_post_SAT_D47_monthly_aggregated_HU_D47, return_covar = True)[1]))
SAT_means_aggregated_hu_d18o = D47c.OGLS23.T47(D47 = mu_post_SAT_D47_monthly_aggregated_HU_d18O, sD47 = cov_post_SAT_D47_monthly_aggregated_HU_d18O, return_covar = True)[0]
SAT_stds_aggregated_hu_d18o = np.sqrt(np.diag(D47c.OGLS23.T47(D47 = mu_post_SAT_D47_monthly_aggregated_HU_d18O, sD47 = cov_post_SAT_D47_monthly_aggregated_HU_d18O, return_covar = True)[1]))
SAT_means_aggregated_hu = D47c.OGLS23.T47(D47 = mu_post_SAT_D47_monthly_aggregated_HU, sD47 = cov_post_SAT_D47_monthly_aggregated_HU, return_covar = True)[0]
SAT_stds_aggregated_hu = np.sqrt(np.diag(D47c.OGLS23.T47(D47 = mu_post_SAT_D47_monthly_aggregated_HU, sD47 = cov_post_SAT_D47_monthly_aggregated_HU, return_covar = True)[1]))
SAT_means_aggregated_lu_d47 = D47c.OGLS23.T47(D47 = mu_post_SAT_D47_monthly_aggregated_LU_D47, sD47 = cov_post_SAT_D47_monthly_aggregated_LU_D47, return_covar = True)[0]
SAT_stds_aggregated_lu_d47 = np.sqrt(np.diag(D47c.OGLS23.T47(D47 = mu_post_SAT_D47_monthly_aggregated_LU_D47, sD47 = cov_post_SAT_D47_monthly_aggregated_LU_D47, return_covar = True)[1]))
SAT_means_aggregated_lu_d18o = D47c.OGLS23.T47(D47 = mu_post_SAT_D47_monthly_aggregated_LU_d18O, sD47 = cov_post_SAT_D47_monthly_aggregated_LU_d18O, return_covar = True)[0]
SAT_stds_aggregated_lu_d18o = np.sqrt(np.diag(D47c.OGLS23.T47(D47 = mu_post_SAT_D47_monthly_aggregated_LU_d18O, sD47 = cov_post_SAT_D47_monthly_aggregated_LU_d18O, return_covar = True)[1]))
SAT_means_aggregated_lu = D47c.OGLS23.T47(D47 = mu_post_SAT_D47_monthly_aggregated_LU, sD47 = cov_post_SAT_D47_monthly_aggregated_LU, return_covar = True)[0]
SAT_stds_aggregated_lu = np.sqrt(np.diag(D47c.OGLS23.T47(D47 = mu_post_SAT_D47_monthly_aggregated_LU, sD47 = cov_post_SAT_D47_monthly_aggregated_LU, return_covar = True)[1]))
# Combine all means and standard deviations into the data structure
all_SST_means = [
SST_means_prior,
mu_measurement_SST_monthly,
SST_means_individual,
SST_means_individual_hu_d47,
SST_means_individual_hu_d18o,
SST_means_individual_hu,
SST_means_individual_lu_d47,
SST_means_individual_lu_d18o,
SST_means_individual_lu,
SST_means_aggregated,
SST_means_aggregated_hu_d47,
SST_means_aggregated_hu_d18o,
SST_means_aggregated_hu,
SST_means_aggregated_lu_d47,
SST_means_aggregated_lu_d18o,
SST_means_aggregated_lu
]
all_SST_stds = [
SST_stds_prior,
std_measurement_SST_monthly,
SST_stds_individual,
SST_stds_individual_hu_d47,
SST_stds_individual_hu_d18o,
SST_stds_individual_hu,
SST_stds_individual_lu_d47,
SST_stds_individual_lu_d18o,
SST_stds_individual_lu,
SST_stds_aggregated,
SST_stds_aggregated_hu_d47,
SST_stds_aggregated_hu_d18o,
SST_stds_aggregated_hu,
SST_stds_aggregated_lu_d47,
SST_stds_aggregated_lu_d18o,
SST_stds_aggregated_lu
]
all_SAT_means = [
SAT_means_prior,
mu_measurement_SAT_monthly,
SAT_means_individual,
SAT_means_individual_hu_d47,
SAT_means_individual_hu_d18o,
SAT_means_individual_hu,
SAT_means_individual_lu_d47,
SAT_means_individual_lu_d18o,
SAT_means_individual_lu,
SAT_means_aggregated,
SAT_means_aggregated_hu_d47,
SAT_means_aggregated_hu_d18o,
SAT_means_aggregated_hu,
SAT_means_aggregated_lu_d47,
SAT_means_aggregated_lu_d18o,
SAT_means_aggregated_lu
]
all_SAT_stds = [
SAT_stds_prior,
std_measurement_SAT_monthly,
SAT_stds_individual,
SAT_stds_individual_hu_d47,
SAT_stds_individual_hu_d18o,
SAT_stds_individual_hu,
SAT_stds_individual_lu_d47,
SAT_stds_individual_lu_d18o,
SAT_stds_individual_lu,
SAT_stds_aggregated,
SAT_stds_aggregated_hu_d47,
SAT_stds_aggregated_hu_d18o,
SAT_stds_aggregated_hu,
SAT_stds_aggregated_lu_d47,
SAT_stds_aggregated_lu_d18o,
SAT_stds_aggregated_lu
]
# Create a dictionary to store all the data
all_temperature_data = {"Data Type": data["Data Type"]}
# Add SST means and stds
for i, month in enumerate(months):
all_temperature_data[f"{month} SST Mean"] = [means[i] for means in all_SST_means]
all_temperature_data[f"{month} SST Std"] = [stds[i] for stds in all_SST_stds]
# Add SAT means and stds
for i, month in enumerate(months):
all_temperature_data[f"{month} SAT Mean"] = [means[i] for means in all_SAT_means]
all_temperature_data[f"{month} SAT Std"] = [stds[i] for stds in all_SAT_stds]
# Create the Pandas DataFrame
all_temperature_uncertainty_test_table_df = pd.DataFrame(all_temperature_data)
# Set the 'Data Type' column as the index
all_temperature_uncertainty_test_table_df = all_temperature_uncertainty_test_table_df.set_index("Data Type")
# Export the table to a CSV file
all_temperature_uncertainty_test_table_df.to_csv("Sensitivity_test_D47_d18Oc_uncertainties/all_temperature_uncertainty_test_table.csv")
print("Combined data exported to Sensitivity_test_D47_d18Oc_uncertainties/all_temperature_uncertainty_test_table.csv")
Combined data exported to Sensitivity_test_D47_d18Oc_uncertainties/all_temperature_uncertainty_test_table.csv
Combined plot of measurements, priors and posteriors with precision uncertainties for temperatureΒΆ
InΒ [55]:
fig, (ax1, ax3) = plt.subplots(
2, 1, figsize=(10, 8), sharex='col'
)
fig.subplots_adjust(hspace=0.1, wspace=0.25) # reduce vertical & horizontal spacing
# Define a colormap across all months
cmap = plt.cm.coolwarm
colors = [cmap(i / (len(months)-1)) for i in range(len(months))]
color_dict = dict(zip(months, colors))
# Define offsets for x-axis to avoid overlapping error bars
x_offsets = np.linspace(-0.3, 0.3, len(months))
# Define x values for each data type
x_values = np.arange(len(all_temperature_uncertainty_test_table_df.index))
# --- SST plot ---
for month in months:
ax1.errorbar(
x = x_values + x_offsets[months.index(month)],
y = all_temperature_uncertainty_test_table_df[f"{month} SST Mean"],
yerr = all_temperature_uncertainty_test_table_df[f"{month} SST Std"],
capsize = 3,
fmt='.',
label=month,
color=color_dict[month]
)
# Add light green rectangle to highlight prior
ax1.add_patch(
Rectangle(
(-0.5, ax1.get_ylim()[0]), # (x,y)
1, # width
ax1.get_ylim()[1] - ax1.get_ylim()[0], # height
color='lightgreen',
alpha=0.3,
)
)
# Add light blue rectangle to highlight measurement
ax1.add_patch(
Rectangle(
(0.5, ax1.get_ylim()[0]), # (x,y)
1, # width
ax1.get_ylim()[1] - ax1.get_ylim()[0], # height
color='lightblue',
alpha=0.3,
)
)
# Add light red rectangle to highlight posteriors
ax1.add_patch(
Rectangle(
(1.5, ax1.get_ylim()[0]), # (x,y)
14, # width
ax1.get_ylim()[1] - ax1.get_ylim()[0], # height
color='red',
alpha=0.1,
)
)
ax1.set_xticks(x_values)
ax1.set_xticklabels(all_temperature_uncertainty_test_table_df.index, rotation=45, ha="right")
# ax1.set_xlabel('test case')
ax1.set_ylabel('SST (degrees C)', color='purple')
ax1.tick_params(axis='y', labelcolor='purple')
# # Secondary axis for temperature
# ax1_temp = ax1.twinx()
# ax1_temp.set_ylabel('SST Temperature (Β°C)', color='purple')
# ax1_temp.tick_params(axis='y', labelcolor='purple')
# ax1_temp.set_yticks(
# ticks = D47c.OGLS23.T47(T = np.linspace(0, 50, 25))[0],
# labels = np.linspace(0, 50, 25).astype(int)
# )
# ax1_temp.set_ylim(ax1.get_ylim())
# --- SAT Plot ---
for month in months:
ax3.errorbar(
x = x_values + x_offsets[months.index(month)],
y = all_temperature_uncertainty_test_table_df[f"{month} SAT Mean"],
yerr = all_temperature_uncertainty_test_table_df[f"{month} SAT Std"],
capsize = 3,
fmt='.',
label=month,
color=color_dict[month]
)
# Add light green rectangle to highlight prior
ax3.add_patch(
Rectangle(
(-0.5, ax3.get_ylim()[0]), # (x,y)
1, # width
ax3.get_ylim()[1] - ax3.get_ylim()[0], # height
color='lightgreen',
alpha=0.3,
)
)
# Add light blue rectangle to highlight measurement
ax3.add_patch(
Rectangle(
(0.5, ax3.get_ylim()[0]), # (x,y)
1, # width
ax3.get_ylim()[1] - ax3.get_ylim()[0], # height
color='lightblue',
alpha=0.3,
)
)
# Add light red rectangle to highlight posteriors
ax3.add_patch(
Rectangle(
(1.5, ax3.get_ylim()[0]), # (x,y)
14, # width
ax3.get_ylim()[1] - ax3.get_ylim()[0], # height
color='red',
alpha=0.1,
)
)
ax3.set_xticks(x_values)
ax3.set_xticklabels(all_temperature_uncertainty_test_table_df.index, rotation=45, ha="right")
ax3.set_ylabel('SAT (degrees C)', color='darkblue')
ax3.tick_params(axis='y', labelcolor='darkblue')
# # Secondary axis for temperature, based on d18Oc-temperature conversion used above
# ax3_temp = ax3.twinx()
# ax3_temp.set_ylabel('SAT Temperature (Β°C; assuming Ξ΄18Ow of 0β°)', color='darkblue')
# ax3_temp.tick_params(axis='y', labelcolor='darkblue')
# ax3_temp.set_yticks(
# ticks = ((20.6 - np.linspace(0, 50, 25)) / 4.34 - 0.27) + 0 + 0.27,
# labels = np.linspace(0, 50, 25).astype(int)
# )
# ax3_temp.set_ylim(ax3.get_ylim())
# ax3.set_title('SAT value by test case and Month')
# ---------------------------------------------------------
# Shared legend above all plots
handles1, labels1 = ax1.get_legend_handles_labels()
fig.legend(handles1, labels1,
loc="upper center",
bbox_to_anchor=(0.5, 0.95),
ncol=len(months) / 2,
frameon=False)
plt.suptitle('Sensitivity Test Results: Temperature Estimates under Different Uncertainty Scenarios', fontsize=16)
plt.tight_layout(rect=[0, 0, 1, 0.95]) # leave space for legend
plt.show()
Potential avenues for further testing/exploration:ΒΆ
- Effect of increased/reduced uncertainty of inputs on other assembled climate variables
- Aggregate table with statistics of outcomes contingent on uncertainty (annual means and seasonal range +/- uncertainty of posterior)